Intro

Method

Summary data prep

In brief: Reducing dataset from >3 million rows where ~5,500 households each have up to 829 observations (1 per date per household) to a summary table of features describing each household’s energy consumption behaviour (1 row per household).

Features are based on summer/winter seasons, namely the most recent ones in the available data: Summer 2013 (June - August 2013) and Winter 2013 (December 2013 - February 2014), and many calculated variables are removed from the clustering dataset to avoid multi-colinearity and try to ensure variables are as independent as they could be.

For full exploration leading to the cleaning and feature engineering steps, refer to:

  • data_exploration.Rmd, household_energy_stats.Rmd – initial exploration, understanding the full dataset (incl. over time)
  • weather_data_exploration.Rmd – looking at weather v season variables as potential predictors
  • feature_engineering_hhold_clustering.Rmd – refining the data prep and k means clustering method used here

Setup and load data from files

library(tidyverse)
library(cluster)
library(factoextra)
library(dendextend)
library(corrplot)
joined_all <- read_csv("../4_cleaned_data/daily_energy_weather_all.csv") %>% 
  mutate(yearseason = factor(yearseason, levels = c(
    "Autumn 2011", "Winter 2011", "Spring 2012", "Summer 2012",
    "Autumn 2012", "Winter 2012", "Spring 2013", "Summer 2013",
    "Autumn 2013", "Winter 2013"
  )))
Rows: 3505100 Columns: 21── Column specification ───────────────────────────────────────────────
Delimiter: ","
chr   (6): lc_lid, month, season, yearseason, wday, temp_qual
dbl  (10): kwh, cloud_cover, sunshine, global_radiation, max_temp, ...
lgl   (3): weekend, precipitation_tf, snow_tf
date  (2): date, quarter
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.

Reduce full dataset to only Summer and Winter 2013, include only households with at least ~50% (45 days’ worth) of data in each season.

threshold <- 45 # minimum number values per season per hh

# list hholds to include
winter_summer_2013_hholds <- joined_all %>% 
  group_by(yearseason, lc_lid) %>% 
  summarise(count = n()) %>% 
  ungroup() %>% 
  filter(yearseason %in% c("Summer 2013", "Winter 2013")) %>% 
  # keep only hholds with >x values in each season
  filter(count >= threshold) %>% 
  group_by(lc_lid) %>% 
  # find which households in both seasons
  summarise(num_seasons = n()) %>% # 5283 households in either
  filter(num_seasons == 2) %>%  # 4999 households in both
  pull(lc_lid)
`summarise()` has grouped output by 'yearseason'. You can override using the `.groups` argument.
# size of data to include in next steps
joined_all %>% 
  filter(lc_lid %in% winter_summer_2013_hholds) %>% 
  filter(yearseason %in% c("Summer 2013", "Winter 2013")) %>% 
  group_by(yearseason, lc_lid) %>% 
  summarise(count = n()) %>% 
  ungroup() %>% 
  group_by(yearseason) %>% 
  summarise(num_hh = n())
`summarise()` has grouped output by 'yearseason'. You can override using the `.groups` argument.

n = 5,078 households in each season

# make summer/winter subset
summer_winter2013 <- joined_all %>% 
  filter(yearseason %in% c("Summer 2013", "Winter 2013"),
         lc_lid %in% winter_summer_2013_hholds)

summer_winter2013

Create summary features

Make dataframe with 1 row per household describing energy consumption behaviour, according to key features: seasonal change, within-season average and variance, pattern of usage by weekday type (weekend or Mon-Fri as typical working week).

Note: infer change = 0 if comparator mean values both extremely small, otherwise small / very small gives large change value when realsitically it is changing from effectively 0 to 0, no change.

summer_mean <- summer_winter2013 %>% 
  filter(yearseason == "Summer 2013") %>% 
  group_by(lc_lid) %>% 
  mutate(summer_mean_kwh = mean(kwh)) %>%
  ungroup() %>% 
  select(lc_lid, summer_mean_kwh) %>% 
  unique()

winter_mean <- summer_winter2013 %>% 
  filter(yearseason == "Winter 2013") %>% 
  group_by(lc_lid) %>% 
  mutate(winter_mean_kwh = mean(kwh)) %>%
  ungroup() %>% 
  select(lc_lid, winter_mean_kwh) %>% 
  unique()

summer_variance <- summer_winter2013 %>% 
  filter(yearseason == "Summer 2013") %>% 
  group_by(lc_lid) %>% 
  mutate(summer_sd = sd(kwh)) %>%
  ungroup() %>% 
  select(lc_lid, summer_sd) %>% 
  unique()

winter_variance <- summer_winter2013 %>% 
  filter(yearseason == "Winter 2013") %>% 
  group_by(lc_lid) %>% 
  mutate(winter_sd = sd(kwh)) %>%
  ungroup() %>% 
  select(lc_lid, winter_sd) %>% 
  unique()

winter_weekend_mean <- summer_winter2013 %>% 
  filter(yearseason == "Winter 2013" & weekend) %>% 
  group_by(lc_lid) %>% 
  mutate(winter_wkend_mean = mean(kwh)) %>%
  ungroup() %>% 
  select(lc_lid, winter_wkend_mean) %>% 
  unique()

winter_weekday_mean <- summer_winter2013 %>% 
  filter(yearseason == "Winter 2013" & !weekend) %>% 
  group_by(lc_lid) %>% 
  mutate(winter_wkday_mean = mean(kwh)) %>%
  ungroup() %>% 
  select(lc_lid, winter_wkday_mean) %>% 
  unique()

summer_weekend_mean <- summer_winter2013 %>% 
  filter(yearseason == "Summer 2013" & weekend) %>% 
  group_by(lc_lid) %>% 
  mutate(summer_wkend_mean = mean(kwh)) %>%
  ungroup() %>% 
  select(lc_lid, summer_wkend_mean) %>% 
  unique()

summer_weekday_mean <- summer_winter2013 %>% 
  filter(yearseason == "Summer 2013" & !weekend) %>% 
  group_by(lc_lid) %>% 
  mutate(summer_wkday_mean = mean(kwh)) %>%
  ungroup() %>% 
  select(lc_lid, summer_wkday_mean) %>% 
  unique()

Create features based on change between seasons and weekday types:

summer_wkend_chg <- left_join(summer_weekend_mean, summer_weekday_mean) %>% 
  mutate(summer_wkend_pc_change = if_else(
    summer_wkend_mean < 0.5 & summer_wkday_mean < 0.5, 0,
    (100 * (summer_wkend_mean - summer_wkday_mean) / summer_wkday_mean)))
Joining with `by = join_by(lc_lid)`
summer_wkend_chg %>%
  #filter(summer_wkend_mean < 0.5 & summer_wkday_mean < 0.5)
  filter(summer_wkend_pc_change == 0) 
  # all 0 values are inferred, this has inferred 22 zeros

summer_wkend_chg %>% 
  ggplot() +
  geom_histogram(aes(x = summer_wkend_pc_change), bins = 30)

Inferring 0 where values are small gives a normal-looking distribution, with reasonable x axis limits (-100 % to +150 % change)

winter_wkend_chg <- left_join(winter_weekend_mean, winter_weekday_mean) %>% 
  mutate(winter_wkend_pc_change = if_else(
    winter_wkend_mean < 0.5 & winter_wkday_mean < 0.5, 0,
    (100 * (winter_wkend_mean - winter_wkday_mean) / winter_wkday_mean)))
Joining with `by = join_by(lc_lid)`
winter_wkend_chg %>%
  #filter(winter_wkend_mean < 0.5 & winter_wkday_mean < 0.5)
  filter(winter_wkend_pc_change == 0) 
  # All 24 zero values are inferred

winter_wkend_chg %>% 
  ggplot() +
  geom_histogram(aes(x = winter_wkend_pc_change), bins = 30)

# also nice distribution

Again, inferring change = 0 is helpful (similar to the above).

winter_mean_change <- inner_join(summer_mean, winter_mean) %>% 
  # recode lowest mean values to 0.5 kWh (as effectively none)
  mutate(summer_mean_kwh_imputed = if_else(summer_mean_kwh < 0.5, 0.5, summer_mean_kwh),
         winter_mean_kwh_imputed = if_else(winter_mean_kwh < 0.5, 0.5, winter_mean_kwh)) %>% 
  # calculate percentage change (with condition to set ~0/ ~0 as 0)
  mutate(winter_fold_change = if_else(
    winter_mean_kwh_imputed < 0.5 & summer_mean_kwh_imputed < 0.5, 0,
    (winter_mean_kwh_imputed - summer_mean_kwh_imputed) / summer_mean_kwh_imputed))
Joining with `by = join_by(lc_lid)`
winter_mean_change %>%
  filter(winter_mean_kwh < 0.5 & summer_mean_kwh < 0.5)
  #filter(winter_fold_change == 0) 
  # all 11 zero values are inferred for the 11 hhouseholds with both low

winter_mean_change %>% 
  ggplot() +
  geom_histogram(aes(x = winter_fold_change), bins = 30)

winter_mean_change %>% 
  select(-summer_mean_kwh, -winter_mean_kwh) %>% 
  filter(winter_fold_change > 10) %>% 
  arrange(desc(winter_fold_change)) %>% 
  arrange(summer_mean_kwh_imputed)

winter_mean_change %>% 
  filter(summer_mean_kwh < 0.5 & winter_mean_kwh < 0.5)

Imputing low mean kWh values (i.e. < 0.5 kWh) to a minimum of 0.5 kWh helps keep the calculated fold change reasonable. Although note that for some, this dramatically reduces the fold change - this imputed value affects 37 households, of which 11 households have imputed values for both winter and summer mean, effectively making their seasonal change 0.

The resulting winter_fold_change variables is still right-skewed but is on a more reasonable scale than calculating percentage change and without any imputations or low-value conditions (e.g. extreme value at 5000% versus many at <10%). Note: these data will be scaled later for clustering, so this skewness will be less of an issue.

# this table is not used in join, instead use winter_sd_change
winter_var_change <- inner_join(summer_variance, winter_variance) %>% 
  mutate(seasonal_rel_chg_in_sd = winter_sd / summer_sd)
Joining with `by = join_by(lc_lid)`
## set lowest bound sd as 0.1 kWh, impute lower values as 0.1
winter_var_change %>% 
  filter(winter_sd < 0.5) %>% 
  ggplot() +
  geom_histogram(aes(x = winter_sd), bins = 50)


winter_var_change %>% 
  filter(summer_sd < 0.5) %>% 
  ggplot() +
  geom_histogram(aes(x = summer_sd), bins = 50)

Set lowest bound sd as 0.1 kWh, impute lower values as 0.1 - thus cutoff won’t affect many households (~10-15, see above histograms) but will ensure the maths stays sensible.

winter_sd_change <- inner_join(summer_variance, winter_variance) %>% 
  # recode lowest sd values to 0.1 kWh (as effectively none)
  mutate(summer_sd_imputed = if_else(summer_sd < 0.1, 0.1, summer_sd),
         winter_sd_imputed = if_else(winter_sd < 0.1, 0.1, winter_sd)) %>% 
  # calculate fold change (with condition to set ~0/ ~0 as 0)
  mutate(winter_fold_chg_sd = if_else(winter_sd < 0.1 & summer_sd < 0.1, 0, 
                                      winter_sd_imputed / summer_sd_imputed))
Joining with `by = join_by(lc_lid)`
winter_sd_change %>%
  #filter(winter_sd < 0.1 & summer_sd < 0.1)
  filter(winter_fold_chg_sd == 0) 
  # all 9 zero values are inferred for the 9 hhouseholds with both low

winter_sd_change %>% 
  ggplot() +
  geom_histogram(aes(x = winter_fold_chg_sd), bins = 30)

Right skewed with a few higher values, but reasonable scale for fold change (0 to 80)

Join summary data

The above feature engineering created multiple tables with household id and summary stat(s), as listed below, and of which the ** tables contain the features to join in a summary table for clustering:

  • summer_mean
  • **winter_mean
  • summer_variance
  • winter_variance
  • winter_weekend_mean
  • winter_weekday_mean
  • summer_weekend_mean
  • summer_weekday_mean
  • **summer_wkend_chg
  • **winter_wkend_chg
  • **winter_mean_change
  • **winter_sd_change
# build summary table, type of join doesn't matter
summary_df <- inner_join(winter_mean, summer_wkend_chg) %>% 
  inner_join(winter_wkend_chg) %>% 
  inner_join(winter_mean_change) %>% 
  inner_join(winter_sd_change) %>% 
  # remove columns that lead to multicolinearity
  select(lc_lid, winter_mean_kwh, 
         summer_wkend_pc_change, winter_wkend_pc_change,
         winter_fold_change, winter_fold_chg_sd)
Joining with `by = join_by(lc_lid)`Joining with `by = join_by(lc_lid)`Joining with `by = join_by(lc_lid, winter_mean_kwh)`Joining with `by = join_by(lc_lid)`
colnames(summary_df)
[1] "lc_lid"                 "winter_mean_kwh"       
[3] "summer_wkend_pc_change" "winter_wkend_pc_change"
[5] "winter_fold_change"     "winter_fold_chg_sd"    
dim(summary_df)
[1] 5078    6

Summary table contains data on 5078 households, with id plus 5 attributes

K-means clustering optimisation

Steps for K-means:

  1. Have named rows
  2. Scale data
  3. Understand correlations between vars (remember no outcome var here) to help us understand our data
  4. Do k-means clustering and find which clusters each row belong to
  5. Do k-means clustering for range of k values and find out which number of clusters best fits the data (according to different methods)
  6. Plot the data and colour by cluster to visualise

1+2 data prep for k-means clustering

summary_df %>% 
  # name the rows with household id
  column_to_rownames("lc_lid") %>%
  # mean_kwh and winter changes (mean, sd) are all right-skewed so log transform these values
  mutate(across(.cols = c(winter_mean_kwh, winter_fold_change, winter_fold_chg_sd),
                .fns = ~ log(.x + 1), # plus 1 to avoid NaN and -Inf
                .names = "log_{.col}_p1")) %>% 
  skimr::skim()
── Data Summary ────────────────────────
                           Values    
Name                       Piped data
Number of rows             5078      
Number of columns          8         
_______________________              
Column type frequency:               
  numeric                  8         
________________________             
Group variables            None      
  # 762 NAs in log(winter_fold_change) because have neg fold change values, lowest is -0.92, so +1 to all for log (new no change, 0, is log(1)) --> log(winter_fold_change + 1)

log(value) –> NaN (if value <0) or -Inf (if value == 0), so +1 to all values to ensure > 0.

i.e. 762 NaNs in log(winter_fold_change) because have they have negative fold change values, lowest is -0.92, so +1 to all values is sufficient for log transformation (log(value + 1) –> new “no change” or 0 fold_change is now log(1)).

# prep data for clustering
summary_df_log <- summary_df %>% 
  # mean_kwh and winter changes (mean, sd) are all right-skewed so log transform these values
  mutate(across(.cols = c(winter_mean_kwh, winter_fold_change, winter_fold_chg_sd),
                .fns = ~ log(.x + 1), # plus 1 to avoid NaN and -Inf
                .names = "log_{.col}_p1"))

summary_df_log

log_winter_fold_chg_sd_p1 is still right-skewed, but the other two are more normal now.

colnames(summary_df_log)
[1] "lc_lid"                    "winter_mean_kwh"           "summer_wkend_pc_change"   
[4] "winter_wkend_pc_change"    "winter_fold_change"        "winter_fold_chg_sd"       
[7] "log_winter_mean_kwh_p1"    "log_winter_fold_change_p1" "log_winter_fold_chg_sd_p1"
# prep data for clustering
df_cluster <- summary_df_log %>%   
  # name the rows with household id
  column_to_rownames("lc_lid") %>%
  # keep only the vars of interest for clustering
  select(log_winter_mean_kwh_p1, log_winter_fold_change_p1, log_winter_fold_chg_sd_p1, summer_wkend_pc_change, winter_wkend_pc_change) %>% 
  # scale the data (normal around mean, sd 0,1)
  mutate(across(where(is.numeric), scale))
  
# see correlations between values
corrplot(cor(df_cluster), method = "number", type = "lower")

Moderate positive correlations between:

  • Summer and winter weekend v weekday change
  • Winter/summer fold change ~ winter mean
  • Winter/summer change in sd ~ Winter/summer fold change
# Summer and winter weekend v weekday change
summary_df_log %>% 
  ggplot() +
  geom_point(aes(x = summer_wkend_pc_change, y = winter_wkend_pc_change))

# Winter/summer fold change ~ winter mean
summary_df_log %>% 
  #filter(seasonal_rel_chg_in_sd < 100) %>% # zoom in!
  ggplot() +
  geom_point(aes(x = log_winter_mean_kwh_p1, y = log_winter_fold_change_p1))

summary_df_log %>% 
  #filter(seasonal_rel_chg_in_sd < 100) %>% # zoom in!
  ggplot() +
  geom_point(aes(x = log_winter_fold_change_p1, y = log_winter_fold_chg_sd_p1))

for all three, most households are in a single cluster in the middle, some values out on the edges.

Not clear clusters here, but could be groups in multi-dimensional spaces (e.g. “average” households versus thos on the edge)

K means optimisation

max_k <- 10

k_clusters <- tibble(k = 1:max_k) %>% 
  mutate(kclust = map(k, ~ kmeans(df_cluster, .x, iter.max = 15, nstart = 25)),
         tidied = map(kclust, tidy),
         glanced = map(kclust, glance),
         augmented = map(kclust, augment, df_cluster))

k_clusters

Evaluation: elbow, silhouette methods

evalute using these 2 methods, skip gap stat (too high processing demand)

Elbow method:

The goal here is to find the minimum tot.withinss but with the fewest clusters possible - it’s a cost function to find the best gain (here, loss!) in tot.withinss at the least cost in adding k.

clustering <- k_clusters %>% 
  unnest(glanced)

clustering
ggplot(clustering, aes(x = k, y = tot.withinss)) +
  geom_point() +
  geom_line() +
  scale_x_continuous(breaks = seq(1, 20, by = 1))

Elbow at k = 4

Silhouette method

Note: silhouette method gives a measure of how similar an object is to its own cluster (cohesion) compared to other clusters (separation) – https://towardsdatascience.com/silhouette-method-better-than-elbow-method-to-find-optimal-clusters-378d62ff6891

fviz_nbclust(df_cluster,
             kmeans,
             method = "silhouette",
             nstart = 25)

This suggest optimal k is 2.

Visually inspect k = 2, k = 3, k = 4

summer v winter weekends

# k = 2
clustering %>% 
  unnest(cols = c(augmented)) %>% 
  filter(k <= 2) %>% 
  ggplot(aes(x = summer_wkend_pc_change, y = winter_wkend_pc_change)) +
  geom_point(aes(colour = .cluster, fill = .cluster), size = 1, alpha = 0.2) +
  scale_colour_manual(values = c("mediumblue", "goldenrod1")) +
  scale_fill_manual(values = c("mediumblue", "goldenrod1"))


# k = 3
clustering %>% 
  unnest(cols = c(augmented)) %>% 
  filter(k <= 3) %>% 
  ggplot(aes(x = summer_wkend_pc_change, y = winter_wkend_pc_change)) +
  geom_point(aes(colour = .cluster, fill = .cluster), size = 1, alpha = 0.2) +
  scale_colour_manual(values = c("mediumblue", "goldenrod1", "deeppink1")) +
  scale_fill_manual(values = c("mediumblue", "goldenrod1", "deeppink1")) #+

  #facet_wrap(~ .cluster, ncol = 1)

# k = 4
clustering %>% 
  unnest(cols = c(augmented)) %>% 
  filter(k <= 4) %>% 
  #filter(.cluster == 4) %>%  # filter to see each cluster
  ggplot(aes(x = summer_wkend_pc_change, y = winter_wkend_pc_change)) +
  geom_point(aes(colour = .cluster, fill = .cluster), size = 1, alpha = 0.2) +
  scale_colour_manual(values = c("mediumblue", "goldenrod1", "deeppink1", "lightgreen")) +
  scale_fill_manual(values = c("mediumblue", "goldenrod1", "deeppink1", "lightgreen")) +
  facet_wrap(~ .cluster)

With k = 2 –> there is no real distinction along this plane

With k = 3 –> there is clear separation of cluster 1 into low changers and cluster 2 into high changers.

With k = 4 –> clusters 3 and 4 are “low weekday-type changers” (i.e. clustered in bottom-left of the mass), clusters 1 and 2 cover most of the main mass of points, not necessarily “high changers”

winter mean ~ winter/summer change

# k = 2
clustering %>% 
  unnest(cols = c(augmented)) %>% 
  filter(k <= 2) %>% 
  ggplot(aes(x = log_winter_mean_kwh_p1, y = log_winter_fold_change_p1)) +
  geom_point(aes(colour = .cluster, fill = .cluster), size = 1, alpha = 0.2) +
  scale_colour_manual(values = c("mediumblue", "goldenrod1")) +
  scale_fill_manual(values = c("mediumblue", "goldenrod1"))


# k = 3
clustering %>% 
  unnest(cols = c(augmented)) %>% 
  filter(k <= 3) %>% 
  ggplot(aes(x = log_winter_mean_kwh_p1, y = log_winter_fold_change_p1)) +
  geom_point(aes(colour = .cluster, fill = .cluster), size = 1, alpha = 0.2) +
  scale_colour_manual(values = c("mediumblue", "goldenrod1", "deeppink1")) +
  scale_fill_manual(values = c("mediumblue", "goldenrod1", "deeppink1")) +
  facet_wrap(~ .cluster)


# k = 4
clustering %>% 
  unnest(cols = c(augmented)) %>% 
  filter(k <= 4) %>% 
  #filter(.cluster == 4) %>%  # filter to see each cluster
  ggplot(aes(x = log_winter_mean_kwh_p1, y = log_winter_fold_change_p1)) +
  geom_point(aes(colour = .cluster, fill = .cluster), size = 1, alpha = 0.2) +
  scale_colour_manual(values = c("mediumblue", "goldenrod1", "deeppink1", "lightgreen")) +
  scale_fill_manual(values = c("mediumblue", "goldenrod1", "deeppink1", "lightgreen")) +
  facet_wrap(~ .cluster)

With k = 2, cluster 2 seems to be no/low change in winter/summer mean, while cluster 1 is more top-right (higher winter mean, higher winter/summer fold change – remember this is transformed data: scale(log(x + 1)))

With k = 3, cluster 3 is the higher group, cluster 2 is lower/no change, cluster 1 is across the whole mass of points

With k = 4, cluster 4 picks out higher mean with low/no change, the other clusters don’t look particularly meaningful

winter/summer change in mean v sd

# k = 2
clustering %>% 
  unnest(cols = c(augmented)) %>% 
  filter(k <= 2) %>% 
  ggplot(aes(x = log_winter_fold_change_p1, y = log_winter_fold_chg_sd_p1)) +
  geom_point(aes(colour = .cluster, fill = .cluster), size = 1, alpha = 0.2) +
  scale_colour_manual(values = c("mediumblue", "goldenrod1")) +
  scale_fill_manual(values = c("mediumblue", "goldenrod1"))


# k = 3
clustering %>% 
  unnest(cols = c(augmented)) %>% 
  filter(k <= 3) %>% 
  ggplot(aes(x = log_winter_fold_change_p1, y = log_winter_fold_chg_sd_p1)) +
  geom_point(aes(colour = .cluster, fill = .cluster), size = 1, alpha = 0.2) +
  scale_colour_manual(values = c("mediumblue", "goldenrod1", "deeppink1")) +
  scale_fill_manual(values = c("mediumblue", "goldenrod1", "deeppink1")) #+

  #facet_wrap(~ .cluster)

# k = 4
clustering %>% 
  unnest(cols = c(augmented)) %>% 
  filter(k <= 4) %>% 
  #filter(.cluster == 4) %>%  # filter to see each cluster
  ggplot(aes(x = log_winter_fold_change_p1, y = log_winter_fold_chg_sd_p1)) +
  geom_point(aes(colour = .cluster, fill = .cluster), size = 1, alpha = 0.2) +
  scale_colour_manual(values = c("mediumblue", "goldenrod1", "deeppink1", "lightgreen")) +
  scale_fill_manual(values = c("mediumblue", "goldenrod1", "deeppink1", "lightgreen")) +
  facet_wrap(~ .cluster)

With k = 2, clusters 1 and 2 are distinct (bottom-left and top-right)

With k = 3, cluster 3 is more top-right, clusters 1 and 2 more bottom-left

With k = 4, cluster 4 is smallest and picks out the values around 0, but is not distinct from the other 3 clusters, which are also not distinct from each other

Clustering summary

From the above visualisations, it seems k=3 is optimal for separating clusters along correlating axes of interest, although it is not clear that these separations are very strong or meaningful.

Looking at the k-means:

k3_means <- k_clusters %>% 
  unnest(tidied) %>% 
  filter(k==3) %>% 
  select(-c(kclust, glanced, augmented, k)) %>% 
  relocate(cluster, size)

k3_means

Transform k-means back to raw values - reverse scaling then (where applicable) reverse log and add 1.

summary_df_log
# scale = x - mean / sd
winter_mean_kwh_mean <- mean(summary_df_log$log_winter_mean_kwh_p1)
winter_mean_kwh_sd <- sd(summary_df_log$log_winter_mean_kwh_p1)
winter_fold_change_mean <- mean(summary_df_log$log_winter_fold_change_p1)
winter_fold_change_sd <- sd(summary_df_log$log_winter_fold_change_p1)
winter_fold_chg_sd_mean <- mean(summary_df_log$log_winter_fold_chg_sd_p1)
winter_fold_chg_sd_sd <- sd(summary_df_log$log_winter_fold_chg_sd_p1)
summer_wkend_pc_change_mean <- mean(summary_df_log$summer_wkend_pc_change)
summer_wkend_pc_change_sd <- sd(summary_df_log$summer_wkend_pc_change)
winter_wkend_pc_change_mean <- mean(summary_df_log$winter_wkend_pc_change)
winter_wkend_pc_change_sd  <- sd(summary_df_log$winter_wkend_pc_change)
k3_means_untransformed <- k3_means %>% 
  mutate(winter_mean_kwh = exp((log_winter_mean_kwh_p1 * winter_mean_kwh_sd) + winter_mean_kwh_mean) - 1,
         winter_fold_change = exp((log_winter_fold_change_p1 * winter_fold_change_sd) + winter_fold_change_mean) - 1,
         winter_fold_chg_sd = exp((log_winter_fold_chg_sd_p1 * winter_fold_chg_sd_sd) + winter_fold_chg_sd_mean) - 1) %>% 
  mutate(summer_wkend_pc_change = (summer_wkend_pc_change * summer_wkend_pc_change_sd) - summer_wkend_pc_change_mean,
         winter_wkend_pc_change = (winter_wkend_pc_change * winter_wkend_pc_change_sd) - winter_wkend_pc_change_mean) %>% 
  select(-c(log_winter_mean_kwh_p1, log_winter_fold_change_p1, log_winter_fold_chg_sd_p1, withinss)) %>% 
  # name cols as k3_mean
  rename_with(~ paste0("k3_mean_", .x, recycle0 = TRUE), .cols = -c(cluster,size)) %>% 
  rename(cluster_size = size)

k3_means_untransformed

Using k=3 for k-means clustering:

  • Cluster 1 is largest (n = 3,151 households) and this group is centred on: using less energy at the weekend than weekday, not changing much from winter to summer, and with a lower than average energy use in the winter
  • Cluster 2 is second largest (n = 1,117 households) and similar to cluster 1 in winter usage and winter v summer behaviour, except they use more energy at weekends than during weekdays (in both summer and winter)
  • Cluster 3 is the smallest (n = 810 households) and characterised similarly to cluster 1 in weekend behaviour (not much change, not an increase compared to weekday) but they use more than average energy during the winter and increase usage from summer to winter.
joined_clustered %>% 
  distinct(lc_lid)

Note the joined df only contains the 5,078 households that were clustered (inner_join).

Visualisation and insights

Data to draw insights from:

  • summary_df_clustered - 1 row per household, summarising winter/summer and weekend/weekday energy consumption behaviour, with cluster number and cluster means
  • joined_clustered - 1 row per date per household, with individual energy use as well as weather conditions, with cluster number and cluster means

Summary boxplots

Weekend effect

summary_df_clustered %>% 
  mutate(weekend_effect = if_else(.cluster == 2,"cluster 2","clusters 1+3")) %>% 
  ggplot() +
  geom_boxplot(aes(x = weekend_effect, y = summer_wkend_pc_change))


summary_df_clustered %>% 
  mutate(weekend_effect = if_else(.cluster == 2,"cluster 2","clusters 1+3")) %>% 
  ggplot() +
  geom_boxplot(aes(x = weekend_effect, y = winter_wkend_pc_change))

Winter effect

summary_df_clustered %>% 
  mutate(winter_effect = if_else(.cluster == 3,"cluster 3","clusters 1+2")) %>% 
  ggplot() +
  geom_point(aes(x = winter_mean_kwh, y = winter_fold_change, colour = winter_effect)) +
  theme(legend.position = "bottom")

summary_df_clustered %>% 
  mutate(winter_effect = if_else(.cluster == 3,"cluster 3","clusters 1+2")) %>% 
  ggplot() +
  geom_boxplot(aes(x = winter_effect, y = winter_fold_change))


summary_df_clustered %>% 
  mutate(winter_effect = if_else(.cluster == 3,"cluster 3","clusters 1+2")) %>% 
  ggplot() +
  geom_boxplot(aes(x = winter_effect, y = winter_mean_kwh))

time series plots

# time series for households in cluster 3 (winter effect)
joined_clustered %>% 
  filter(.cluster == 3) %>% 
  group_by(date) %>% 
  mutate(median_kwh = median(kwh)) %>% 
  ungroup() %>% 
  ggplot() +
  geom_line(aes(x = date, y = kwh, group = lc_lid), colour = "grey80", alpha = 0.1) +
  geom_line(aes(x = date, y = median_kwh), colour = "indianred") +
  scale_y_continuous(limits = c(0,350)) +
  theme_classic()

# time series for households in cluster 3 (winter effect)
## zoomed in
joined_clustered %>% 
  filter(.cluster == 3) %>% 
  group_by(date) %>% 
  mutate(median_kwh = median(kwh)) %>% 
  ungroup() %>% 
  ggplot() +
  geom_line(aes(x = date, y = kwh, group = lc_lid), colour = "grey80", alpha = 0.1) +
  geom_line(aes(x = date, y = median_kwh), colour = "indianred") +
  scale_y_continuous(limits = c(0,50)) +
  theme_classic()

# time series for households in cluster 2 (weekend effect)
joined_clustered %>% 
  filter(.cluster == 2) %>% 
  group_by(date) %>% 
  mutate(median_kwh = median(kwh)) %>% 
  ungroup() %>% 
  ggplot() +
  geom_line(aes(x = date, y = kwh, group = lc_lid), colour = "grey80", alpha = 0.1) +
  geom_line(aes(x = date, y = median_kwh), colour = "indianred") +
  scale_y_continuous(limits = c(0,350)) +
  theme_classic()

# time series for households in cluster 2 (weekend effect)
## zoomed in on one month (2013-01)
joined_clustered %>% 
  filter(.cluster == 2) %>%
  group_by(date) %>% 
  mutate(median_kwh = median(kwh)) %>% 
  ungroup() %>% 
  filter(date >= "2013-01-01" & date <= "2013-01-31") %>% 
  ggplot() +
  geom_line(aes(x = date, y = kwh, group = lc_lid), colour = "grey80", alpha = 0.1) +
  geom_line(aes(x = date, y = median_kwh), colour = "indianred") +
  scale_y_continuous(limits = c(0,50)) +
  theme_classic()

# time series for households in cluster 1 (low changers)
joined_clustered %>% 
  filter(.cluster == 1) %>% 
  group_by(date) %>% 
  mutate(median_kwh = median(kwh)) %>% 
  ungroup() %>% 
  ggplot() +
  geom_line(aes(x = date, y = kwh, group = lc_lid), colour = "grey80", alpha = 0.1) +
  geom_line(aes(x = date, y = median_kwh), colour = "indianred") +
  scale_y_continuous(limits = c(0,350)) +
  theme_classic()

# time series for households in cluster 1 (low changers)
## zoomed in on y axis
joined_clustered %>% 
  filter(.cluster == 1) %>% 
  group_by(date) %>% 
  mutate(median_kwh = median(kwh)) %>% 
  ungroup() %>% 
  ggplot() +
  geom_line(aes(x = date, y = kwh, group = lc_lid), colour = "grey80", alpha = 0.1) +
  geom_line(aes(x = date, y = median_kwh), colour = "indianred") +
  scale_y_continuous(limits = c(0,50)) +
  theme_classic()

# time series for households in cluster 1 (low changers)
## zoomed in on y axis & for Jan 2013 only
joined_clustered %>% 
  filter(.cluster == 1) %>% 
  group_by(date) %>% 
  mutate(median_kwh = median(kwh)) %>% 
  ungroup() %>% 
  filter(date >= "2013-01-01" & date <= "2013-01-31") %>% 
  ggplot() +
  geom_line(aes(x = date, y = kwh, group = lc_lid), colour = "grey80", alpha = 0.1) +
  geom_line(aes(x = date, y = median_kwh), colour = "indianred") +
  scale_y_continuous(limits = c(0,50)) +
  theme_classic()

ci_level = 0.01 # for 99% CI intervals

# calc CI with sample_mean + ci_level*(sample_sd/(sqrt(sample_size)))

joined_clustered %>% 
  group_by(.cluster, date) %>% 
  summarise(median_kwh = median(kwh),
            mean_kwh = mean(kwh),
            lower_ci = mean(kwh) - (ci_level * (sd(kwh)/sqrt(n()))),
            upper_ci = mean(kwh) + (ci_level * (sd(kwh)/sqrt(n())))) %>% 
  ungroup() %>% 
  ggplot(aes(x = date)) +
  geom_ribbon(aes(ymin = lower_ci, ymax = upper_ci, group = .cluster), colour = "grey", alpha = 0.01) +
  geom_line(aes(y = mean_kwh, colour = .cluster)) +
  theme_classic() +
  theme(legend.position = "bottom")
`summarise()` has grouped output by '.cluster'. You can override using the `.groups` argument.

LS0tCnRpdGxlOiAiQ2x1c3RlcmluZyBob3VzZWhvbGRzIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCgojIyBJbnRybwoKCiMgTWV0aG9kCgoKIyMgU3VtbWFyeSBkYXRhIHByZXAKCkluIGJyaWVmOiBSZWR1Y2luZyBkYXRhc2V0IGZyb20gPjMgbWlsbGlvbiByb3dzIHdoZXJlIH41LDUwMCBob3VzZWhvbGRzIGVhY2ggaGF2ZSB1cCB0byA4Mjkgb2JzZXJ2YXRpb25zICgxIHBlciBkYXRlIHBlciBob3VzZWhvbGQpIHRvIGEgc3VtbWFyeSB0YWJsZSBvZiBmZWF0dXJlcyBkZXNjcmliaW5nIGVhY2ggaG91c2Vob2xkJ3MgZW5lcmd5IGNvbnN1bXB0aW9uIGJlaGF2aW91ciAoMSByb3cgcGVyIGhvdXNlaG9sZCkuCgpGZWF0dXJlcyBhcmUgYmFzZWQgb24gc3VtbWVyL3dpbnRlciBzZWFzb25zLCBuYW1lbHkgdGhlIG1vc3QgcmVjZW50IG9uZXMgaW4gdGhlIGF2YWlsYWJsZSBkYXRhOiBTdW1tZXIgMjAxMyAoSnVuZSAtIEF1Z3VzdCAyMDEzKSBhbmQgV2ludGVyIDIwMTMgKERlY2VtYmVyIDIwMTMgLSBGZWJydWFyeSAyMDE0KSwgYW5kIG1hbnkgY2FsY3VsYXRlZCB2YXJpYWJsZXMgYXJlIHJlbW92ZWQgZnJvbSB0aGUgY2x1c3RlcmluZyBkYXRhc2V0IHRvIGF2b2lkIG11bHRpLWNvbGluZWFyaXR5IGFuZCB0cnkgdG8gZW5zdXJlIHZhcmlhYmxlcyBhcmUgYXMgaW5kZXBlbmRlbnQgYXMgdGhleSBjb3VsZCBiZS4KCkZvciBmdWxsIGV4cGxvcmF0aW9uIGxlYWRpbmcgdG8gdGhlIGNsZWFuaW5nIGFuZCBmZWF0dXJlIGVuZ2luZWVyaW5nIHN0ZXBzLCByZWZlciB0bzoKCiogZGF0YV9leHBsb3JhdGlvbi5SbWQsIGhvdXNlaG9sZF9lbmVyZ3lfc3RhdHMuUm1kIC0tIGluaXRpYWwgZXhwbG9yYXRpb24sIHVuZGVyc3RhbmRpbmcgdGhlIGZ1bGwgZGF0YXNldCAoaW5jbC4gb3ZlciB0aW1lKQoqIHdlYXRoZXJfZGF0YV9leHBsb3JhdGlvbi5SbWQgLS0gbG9va2luZyBhdCB3ZWF0aGVyIHYgc2Vhc29uIHZhcmlhYmxlcyBhcyBwb3RlbnRpYWwgcHJlZGljdG9ycwoqIGZlYXR1cmVfZW5naW5lZXJpbmdfaGhvbGRfY2x1c3RlcmluZy5SbWQgLS0gcmVmaW5pbmcgdGhlIGRhdGEgcHJlcCBhbmQgayBtZWFucyBjbHVzdGVyaW5nIG1ldGhvZCB1c2VkIGhlcmUKCgojIyMjIFNldHVwIGFuZCBsb2FkIGRhdGEgZnJvbSBmaWxlcwoKYGBge3J9CmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KGNsdXN0ZXIpCiNsaWJyYXJ5KGZhY3RvZXh0cmEpICMjIHRoZXNlIGFyZSBmb3IgaGllcmFyY2hpY2FsIGNsdXN0ZXJpbmcKI2xpYnJhcnkoZGVuZGV4dGVuZCkgIyMgdGhlc2UgYXJlIGZvciBoaWVyYXJjaGljYWwgY2x1c3RlcmluZwpsaWJyYXJ5KGNvcnJwbG90KSAjIGZvciBjb3JyZWxhdGlvbiBwbG90CmxpYnJhcnkoYnJvb20pICMgZm9yIGsgbWVhbnMgb3B0aW1pc2F0aW9uIHN0YXRzCmxpYnJhcnkoZ2dzaWduaWYpICMgZm9yIHNpbGhvdWV0dGUgbWV0aG9kCmxpYnJhcnkocnN0YXRpeCkgIyBmb3Igc2lsaG91ZXR0ZSBtZXRob2QKYGBgCgpgYGB7cn0Kam9pbmVkX2FsbCA8LSByZWFkX2NzdigiLi4vNF9jbGVhbmVkX2RhdGEvZGFpbHlfZW5lcmd5X3dlYXRoZXJfYWxsLmNzdiIpICU+JSAKICBtdXRhdGUoeWVhcnNlYXNvbiA9IGZhY3Rvcih5ZWFyc2Vhc29uLCBsZXZlbHMgPSBjKAogICAgIkF1dHVtbiAyMDExIiwgIldpbnRlciAyMDExIiwgIlNwcmluZyAyMDEyIiwgIlN1bW1lciAyMDEyIiwKICAgICJBdXR1bW4gMjAxMiIsICJXaW50ZXIgMjAxMiIsICJTcHJpbmcgMjAxMyIsICJTdW1tZXIgMjAxMyIsCiAgICAiQXV0dW1uIDIwMTMiLCAiV2ludGVyIDIwMTMiCiAgKSkpCmBgYAoKUmVkdWNlIGZ1bGwgZGF0YXNldCB0byBvbmx5IFN1bW1lciBhbmQgV2ludGVyIDIwMTMsIGluY2x1ZGUgb25seSBob3VzZWhvbGRzIHdpdGggYXQgbGVhc3QgfjUwJSAoNDUgZGF5cycgd29ydGgpIG9mIGRhdGEgaW4gZWFjaCBzZWFzb24uCgpgYGB7cn0KdGhyZXNob2xkIDwtIDQ1ICMgbWluaW11bSBudW1iZXIgdmFsdWVzIHBlciBzZWFzb24gcGVyIGhoCgojIGxpc3QgaGhvbGRzIHRvIGluY2x1ZGUKd2ludGVyX3N1bW1lcl8yMDEzX2hob2xkcyA8LSBqb2luZWRfYWxsICU+JSAKICBncm91cF9ieSh5ZWFyc2Vhc29uLCBsY19saWQpICU+JSAKICBzdW1tYXJpc2UoY291bnQgPSBuKCkpICU+JSAKICB1bmdyb3VwKCkgJT4lIAogIGZpbHRlcih5ZWFyc2Vhc29uICVpbiUgYygiU3VtbWVyIDIwMTMiLCAiV2ludGVyIDIwMTMiKSkgJT4lIAogICMga2VlcCBvbmx5IGhob2xkcyB3aXRoID54IHZhbHVlcyBpbiBlYWNoIHNlYXNvbgogIGZpbHRlcihjb3VudCA+PSB0aHJlc2hvbGQpICU+JSAKICBncm91cF9ieShsY19saWQpICU+JSAKICAjIGZpbmQgd2hpY2ggaG91c2Vob2xkcyBpbiBib3RoIHNlYXNvbnMKICBzdW1tYXJpc2UobnVtX3NlYXNvbnMgPSBuKCkpICU+JSAjIDUyODMgaG91c2Vob2xkcyBpbiBlaXRoZXIKICBmaWx0ZXIobnVtX3NlYXNvbnMgPT0gMikgJT4lICAjIDQ5OTkgaG91c2Vob2xkcyBpbiBib3RoCiAgcHVsbChsY19saWQpCgojIHNpemUgb2YgZGF0YSB0byBpbmNsdWRlIGluIG5leHQgc3RlcHMKam9pbmVkX2FsbCAlPiUgCiAgZmlsdGVyKGxjX2xpZCAlaW4lIHdpbnRlcl9zdW1tZXJfMjAxM19oaG9sZHMpICU+JSAKICBmaWx0ZXIoeWVhcnNlYXNvbiAlaW4lIGMoIlN1bW1lciAyMDEzIiwgIldpbnRlciAyMDEzIikpICU+JSAKICBncm91cF9ieSh5ZWFyc2Vhc29uLCBsY19saWQpICU+JSAKICBzdW1tYXJpc2UoY291bnQgPSBuKCkpICU+JSAKICB1bmdyb3VwKCkgJT4lIAogIGdyb3VwX2J5KHllYXJzZWFzb24pICU+JSAKICBzdW1tYXJpc2UobnVtX2hoID0gbigpKQpgYGAKCm4gPSA1LDA3OCBob3VzZWhvbGRzIGluIGVhY2ggc2Vhc29uCgpgYGB7cn0KIyBtYWtlIHN1bW1lci93aW50ZXIgc3Vic2V0CnN1bW1lcl93aW50ZXIyMDEzIDwtIGpvaW5lZF9hbGwgJT4lIAogIGZpbHRlcih5ZWFyc2Vhc29uICVpbiUgYygiU3VtbWVyIDIwMTMiLCAiV2ludGVyIDIwMTMiKSwKICAgICAgICAgbGNfbGlkICVpbiUgd2ludGVyX3N1bW1lcl8yMDEzX2hob2xkcykKCnN1bW1lcl93aW50ZXIyMDEzCmBgYAoKIyMjIyBDcmVhdGUgc3VtbWFyeSBmZWF0dXJlcwoKTWFrZSBkYXRhZnJhbWUgd2l0aCAxIHJvdyBwZXIgaG91c2Vob2xkIGRlc2NyaWJpbmcgZW5lcmd5IGNvbnN1bXB0aW9uIGJlaGF2aW91ciwgYWNjb3JkaW5nIHRvIGtleSBmZWF0dXJlczogc2Vhc29uYWwgY2hhbmdlLCB3aXRoaW4tc2Vhc29uIGF2ZXJhZ2UgYW5kIHZhcmlhbmNlLCBwYXR0ZXJuIG9mIHVzYWdlIGJ5IHdlZWtkYXkgdHlwZSAod2Vla2VuZCBvciBNb24tRnJpIGFzIHR5cGljYWwgd29ya2luZyB3ZWVrKS4KCl9Ob3RlOiBpbmZlciBjaGFuZ2UgPSAwIGlmIGNvbXBhcmF0b3IgbWVhbiB2YWx1ZXMgYm90aCBleHRyZW1lbHkgc21hbGwsIG90aGVyd2lzZSBzbWFsbCAvIHZlcnkgc21hbGwgZ2l2ZXMgbGFyZ2UgY2hhbmdlIHZhbHVlIHdoZW4gcmVhbHNpdGljYWxseSBpdCBpcyBjaGFuZ2luZyBmcm9tIGVmZmVjdGl2ZWx5IDAgdG8gMCwgbm8gY2hhbmdlLl8KCmBgYHtyfQpzdW1tZXJfbWVhbiA8LSBzdW1tZXJfd2ludGVyMjAxMyAlPiUgCiAgZmlsdGVyKHllYXJzZWFzb24gPT0gIlN1bW1lciAyMDEzIikgJT4lIAogIGdyb3VwX2J5KGxjX2xpZCkgJT4lIAogIG11dGF0ZShzdW1tZXJfbWVhbl9rd2ggPSBtZWFuKGt3aCkpICU+JQogIHVuZ3JvdXAoKSAlPiUgCiAgc2VsZWN0KGxjX2xpZCwgc3VtbWVyX21lYW5fa3doKSAlPiUgCiAgdW5pcXVlKCkKCndpbnRlcl9tZWFuIDwtIHN1bW1lcl93aW50ZXIyMDEzICU+JSAKICBmaWx0ZXIoeWVhcnNlYXNvbiA9PSAiV2ludGVyIDIwMTMiKSAlPiUgCiAgZ3JvdXBfYnkobGNfbGlkKSAlPiUgCiAgbXV0YXRlKHdpbnRlcl9tZWFuX2t3aCA9IG1lYW4oa3doKSkgJT4lCiAgdW5ncm91cCgpICU+JSAKICBzZWxlY3QobGNfbGlkLCB3aW50ZXJfbWVhbl9rd2gpICU+JSAKICB1bmlxdWUoKQoKc3VtbWVyX3ZhcmlhbmNlIDwtIHN1bW1lcl93aW50ZXIyMDEzICU+JSAKICBmaWx0ZXIoeWVhcnNlYXNvbiA9PSAiU3VtbWVyIDIwMTMiKSAlPiUgCiAgZ3JvdXBfYnkobGNfbGlkKSAlPiUgCiAgbXV0YXRlKHN1bW1lcl9zZCA9IHNkKGt3aCkpICU+JQogIHVuZ3JvdXAoKSAlPiUgCiAgc2VsZWN0KGxjX2xpZCwgc3VtbWVyX3NkKSAlPiUgCiAgdW5pcXVlKCkKCndpbnRlcl92YXJpYW5jZSA8LSBzdW1tZXJfd2ludGVyMjAxMyAlPiUgCiAgZmlsdGVyKHllYXJzZWFzb24gPT0gIldpbnRlciAyMDEzIikgJT4lIAogIGdyb3VwX2J5KGxjX2xpZCkgJT4lIAogIG11dGF0ZSh3aW50ZXJfc2QgPSBzZChrd2gpKSAlPiUKICB1bmdyb3VwKCkgJT4lIAogIHNlbGVjdChsY19saWQsIHdpbnRlcl9zZCkgJT4lIAogIHVuaXF1ZSgpCgp3aW50ZXJfd2Vla2VuZF9tZWFuIDwtIHN1bW1lcl93aW50ZXIyMDEzICU+JSAKICBmaWx0ZXIoeWVhcnNlYXNvbiA9PSAiV2ludGVyIDIwMTMiICYgd2Vla2VuZCkgJT4lIAogIGdyb3VwX2J5KGxjX2xpZCkgJT4lIAogIG11dGF0ZSh3aW50ZXJfd2tlbmRfbWVhbiA9IG1lYW4oa3doKSkgJT4lCiAgdW5ncm91cCgpICU+JSAKICBzZWxlY3QobGNfbGlkLCB3aW50ZXJfd2tlbmRfbWVhbikgJT4lIAogIHVuaXF1ZSgpCgp3aW50ZXJfd2Vla2RheV9tZWFuIDwtIHN1bW1lcl93aW50ZXIyMDEzICU+JSAKICBmaWx0ZXIoeWVhcnNlYXNvbiA9PSAiV2ludGVyIDIwMTMiICYgIXdlZWtlbmQpICU+JSAKICBncm91cF9ieShsY19saWQpICU+JSAKICBtdXRhdGUod2ludGVyX3drZGF5X21lYW4gPSBtZWFuKGt3aCkpICU+JQogIHVuZ3JvdXAoKSAlPiUgCiAgc2VsZWN0KGxjX2xpZCwgd2ludGVyX3drZGF5X21lYW4pICU+JSAKICB1bmlxdWUoKQoKc3VtbWVyX3dlZWtlbmRfbWVhbiA8LSBzdW1tZXJfd2ludGVyMjAxMyAlPiUgCiAgZmlsdGVyKHllYXJzZWFzb24gPT0gIlN1bW1lciAyMDEzIiAmIHdlZWtlbmQpICU+JSAKICBncm91cF9ieShsY19saWQpICU+JSAKICBtdXRhdGUoc3VtbWVyX3drZW5kX21lYW4gPSBtZWFuKGt3aCkpICU+JQogIHVuZ3JvdXAoKSAlPiUgCiAgc2VsZWN0KGxjX2xpZCwgc3VtbWVyX3drZW5kX21lYW4pICU+JSAKICB1bmlxdWUoKQoKc3VtbWVyX3dlZWtkYXlfbWVhbiA8LSBzdW1tZXJfd2ludGVyMjAxMyAlPiUgCiAgZmlsdGVyKHllYXJzZWFzb24gPT0gIlN1bW1lciAyMDEzIiAmICF3ZWVrZW5kKSAlPiUgCiAgZ3JvdXBfYnkobGNfbGlkKSAlPiUgCiAgbXV0YXRlKHN1bW1lcl93a2RheV9tZWFuID0gbWVhbihrd2gpKSAlPiUKICB1bmdyb3VwKCkgJT4lIAogIHNlbGVjdChsY19saWQsIHN1bW1lcl93a2RheV9tZWFuKSAlPiUgCiAgdW5pcXVlKCkKYGBgCgpDcmVhdGUgZmVhdHVyZXMgYmFzZWQgb24gY2hhbmdlIGJldHdlZW4gc2Vhc29ucyBhbmQgd2Vla2RheSB0eXBlczoKCmBgYHtyfQpzdW1tZXJfd2tlbmRfY2hnIDwtIGxlZnRfam9pbihzdW1tZXJfd2Vla2VuZF9tZWFuLCBzdW1tZXJfd2Vla2RheV9tZWFuKSAlPiUgCiAgbXV0YXRlKHN1bW1lcl93a2VuZF9wY19jaGFuZ2UgPSBpZl9lbHNlKAogICAgc3VtbWVyX3drZW5kX21lYW4gPCAwLjUgJiBzdW1tZXJfd2tkYXlfbWVhbiA8IDAuNSwgMCwKICAgICgxMDAgKiAoc3VtbWVyX3drZW5kX21lYW4gLSBzdW1tZXJfd2tkYXlfbWVhbikgLyBzdW1tZXJfd2tkYXlfbWVhbikpKQoKc3VtbWVyX3drZW5kX2NoZyAlPiUKICAjZmlsdGVyKHN1bW1lcl93a2VuZF9tZWFuIDwgMC41ICYgc3VtbWVyX3drZGF5X21lYW4gPCAwLjUpCiAgZmlsdGVyKHN1bW1lcl93a2VuZF9wY19jaGFuZ2UgPT0gMCkgCiAgIyBhbGwgMCB2YWx1ZXMgYXJlIGluZmVycmVkLCB0aGlzIGhhcyBpbmZlcnJlZCAyMiB6ZXJvcwoKc3VtbWVyX3drZW5kX2NoZyAlPiUgCiAgZ2dwbG90KCkgKwogIGdlb21faGlzdG9ncmFtKGFlcyh4ID0gc3VtbWVyX3drZW5kX3BjX2NoYW5nZSksIGJpbnMgPSAzMCkKYGBgCgpJbmZlcnJpbmcgMCB3aGVyZSB2YWx1ZXMgYXJlIHNtYWxsIGdpdmVzIGEgbm9ybWFsLWxvb2tpbmcgZGlzdHJpYnV0aW9uLCB3aXRoIHJlYXNvbmFibGUgeCBheGlzIGxpbWl0cyAoLTEwMCAlIHRvICsxNTAgJSBjaGFuZ2UpCgpgYGB7cn0Kd2ludGVyX3drZW5kX2NoZyA8LSBsZWZ0X2pvaW4od2ludGVyX3dlZWtlbmRfbWVhbiwgd2ludGVyX3dlZWtkYXlfbWVhbikgJT4lIAogIG11dGF0ZSh3aW50ZXJfd2tlbmRfcGNfY2hhbmdlID0gaWZfZWxzZSgKICAgIHdpbnRlcl93a2VuZF9tZWFuIDwgMC41ICYgd2ludGVyX3drZGF5X21lYW4gPCAwLjUsIDAsCiAgICAoMTAwICogKHdpbnRlcl93a2VuZF9tZWFuIC0gd2ludGVyX3drZGF5X21lYW4pIC8gd2ludGVyX3drZGF5X21lYW4pKSkKCndpbnRlcl93a2VuZF9jaGcgJT4lCiAgI2ZpbHRlcih3aW50ZXJfd2tlbmRfbWVhbiA8IDAuNSAmIHdpbnRlcl93a2RheV9tZWFuIDwgMC41KQogIGZpbHRlcih3aW50ZXJfd2tlbmRfcGNfY2hhbmdlID09IDApIAogICMgQWxsIDI0IHplcm8gdmFsdWVzIGFyZSBpbmZlcnJlZAoKd2ludGVyX3drZW5kX2NoZyAlPiUgCiAgZ2dwbG90KCkgKwogIGdlb21faGlzdG9ncmFtKGFlcyh4ID0gd2ludGVyX3drZW5kX3BjX2NoYW5nZSksIGJpbnMgPSAzMCkKYGBgCgpBZ2FpbiwgaW5mZXJyaW5nIGNoYW5nZSA9IDAgaXMgaGVscGZ1bCAoc2ltaWxhciB0byB0aGUgYWJvdmUpLgoKYGBge3J9CndpbnRlcl9tZWFuX2NoYW5nZSA8LSBpbm5lcl9qb2luKHN1bW1lcl9tZWFuLCB3aW50ZXJfbWVhbikgJT4lIAogICMgcmVjb2RlIGxvd2VzdCBtZWFuIHZhbHVlcyB0byAwLjUga1doIChhcyBlZmZlY3RpdmVseSBub25lKQogIG11dGF0ZShzdW1tZXJfbWVhbl9rd2hfaW1wdXRlZCA9IGlmX2Vsc2Uoc3VtbWVyX21lYW5fa3doIDwgMC41LCAwLjUsIHN1bW1lcl9tZWFuX2t3aCksCiAgICAgICAgIHdpbnRlcl9tZWFuX2t3aF9pbXB1dGVkID0gaWZfZWxzZSh3aW50ZXJfbWVhbl9rd2ggPCAwLjUsIDAuNSwgd2ludGVyX21lYW5fa3doKSkgJT4lIAogICMgY2FsY3VsYXRlIHBlcmNlbnRhZ2UgY2hhbmdlICh3aXRoIGNvbmRpdGlvbiB0byBzZXQgfjAvIH4wIGFzIDApCiAgbXV0YXRlKHdpbnRlcl9mb2xkX2NoYW5nZSA9IGlmX2Vsc2UoCiAgICB3aW50ZXJfbWVhbl9rd2hfaW1wdXRlZCA8IDAuNSAmIHN1bW1lcl9tZWFuX2t3aF9pbXB1dGVkIDwgMC41LCAwLAogICAgKHdpbnRlcl9tZWFuX2t3aF9pbXB1dGVkIC0gc3VtbWVyX21lYW5fa3doX2ltcHV0ZWQpIC8gc3VtbWVyX21lYW5fa3doX2ltcHV0ZWQpKQoKd2ludGVyX21lYW5fY2hhbmdlICU+JQogIGZpbHRlcih3aW50ZXJfbWVhbl9rd2ggPCAwLjUgJiBzdW1tZXJfbWVhbl9rd2ggPCAwLjUpCiAgI2ZpbHRlcih3aW50ZXJfZm9sZF9jaGFuZ2UgPT0gMCkgCiAgIyBhbGwgMTEgemVybyB2YWx1ZXMgYXJlIGluZmVycmVkIGZvciB0aGUgMTEgaGhvdXNlaG9sZHMgd2l0aCBib3RoIGxvdwoKd2ludGVyX21lYW5fY2hhbmdlICU+JSAKICBnZ3Bsb3QoKSArCiAgZ2VvbV9oaXN0b2dyYW0oYWVzKHggPSB3aW50ZXJfZm9sZF9jaGFuZ2UpLCBiaW5zID0gMzApCmBgYAoKYGBge3J9CndpbnRlcl9tZWFuX2NoYW5nZSAlPiUgCiAgc2VsZWN0KC1zdW1tZXJfbWVhbl9rd2gsIC13aW50ZXJfbWVhbl9rd2gpICU+JSAKICBmaWx0ZXIod2ludGVyX2ZvbGRfY2hhbmdlID4gMTApICU+JSAKICBhcnJhbmdlKGRlc2Mod2ludGVyX2ZvbGRfY2hhbmdlKSkgJT4lIAogIGFycmFuZ2Uoc3VtbWVyX21lYW5fa3doX2ltcHV0ZWQpCgp3aW50ZXJfbWVhbl9jaGFuZ2UgJT4lIAogIGZpbHRlcihzdW1tZXJfbWVhbl9rd2ggPCAwLjUgJiB3aW50ZXJfbWVhbl9rd2ggPCAwLjUpCmBgYAoKSW1wdXRpbmcgbG93IG1lYW4ga1doIHZhbHVlcyAoaS5lLiA8IDAuNSBrV2gpIHRvIGEgbWluaW11bSBvZiAwLjUga1doIGhlbHBzIGtlZXAgdGhlIGNhbGN1bGF0ZWQgZm9sZCBjaGFuZ2UgcmVhc29uYWJsZS4gQWx0aG91Z2ggbm90ZSB0aGF0IGZvciBzb21lLCB0aGlzIGRyYW1hdGljYWxseSByZWR1Y2VzIHRoZSBmb2xkIGNoYW5nZSAtIHRoaXMgaW1wdXRlZCB2YWx1ZSBhZmZlY3RzIDM3IGhvdXNlaG9sZHMsIG9mIHdoaWNoIDExIGhvdXNlaG9sZHMgaGF2ZSBpbXB1dGVkIHZhbHVlcyBmb3IgYm90aCB3aW50ZXIgYW5kIHN1bW1lciBtZWFuLCBlZmZlY3RpdmVseSBtYWtpbmcgdGhlaXIgc2Vhc29uYWwgY2hhbmdlIDAuCgpUaGUgcmVzdWx0aW5nIHdpbnRlcl9mb2xkX2NoYW5nZSB2YXJpYWJsZXMgaXMgc3RpbGwgcmlnaHQtc2tld2VkIGJ1dCBpcyBvbiBhIG1vcmUgcmVhc29uYWJsZSBzY2FsZSB0aGFuIGNhbGN1bGF0aW5nIHBlcmNlbnRhZ2UgY2hhbmdlIGFuZCB3aXRob3V0IGFueSBpbXB1dGF0aW9ucyBvciBsb3ctdmFsdWUgY29uZGl0aW9ucyAoZS5nLiBleHRyZW1lIHZhbHVlIGF0IDUwMDAlIHZlcnN1cyBtYW55IGF0IDwxMCUpLiBOb3RlOiB0aGVzZSBkYXRhIHdpbGwgYmUgc2NhbGVkIGxhdGVyIGZvciBjbHVzdGVyaW5nLCBzbyB0aGlzIHNrZXduZXNzIHdpbGwgYmUgbGVzcyBvZiBhbiBpc3N1ZS4KCmBgYHtyfQojIHRoaXMgdGFibGUgaXMgbm90IHVzZWQgaW4gam9pbiwgaW5zdGVhZCB1c2Ugd2ludGVyX3NkX2NoYW5nZQp3aW50ZXJfdmFyX2NoYW5nZSA8LSBpbm5lcl9qb2luKHN1bW1lcl92YXJpYW5jZSwgd2ludGVyX3ZhcmlhbmNlKSAlPiUgCiAgbXV0YXRlKHNlYXNvbmFsX3JlbF9jaGdfaW5fc2QgPSB3aW50ZXJfc2QgLyBzdW1tZXJfc2QpCgp3aW50ZXJfdmFyX2NoYW5nZSAlPiUgCiAgZmlsdGVyKHdpbnRlcl9zZCA8IDAuNSkgJT4lIAogIGdncGxvdCgpICsKICBnZW9tX2hpc3RvZ3JhbShhZXMoeCA9IHdpbnRlcl9zZCksIGJpbnMgPSA1MCkKCndpbnRlcl92YXJfY2hhbmdlICU+JSAKICBmaWx0ZXIoc3VtbWVyX3NkIDwgMC41KSAlPiUgCiAgZ2dwbG90KCkgKwogIGdlb21faGlzdG9ncmFtKGFlcyh4ID0gc3VtbWVyX3NkKSwgYmlucyA9IDUwKQpgYGAKClNldCBsb3dlc3QgYm91bmQgc2QgYXMgMC4xIGtXaCwgaW1wdXRlIGxvd2VyIHZhbHVlcyBhcyAwLjEgLSB0aHVzIGN1dG9mZiB3b24ndCBhZmZlY3QgbWFueSBob3VzZWhvbGRzICh+MTAtMTUsIHNlZSBhYm92ZSBoaXN0b2dyYW1zKSBidXQgd2lsbCBlbnN1cmUgdGhlIG1hdGhzIHN0YXlzIHNlbnNpYmxlLgoKYGBge3J9CndpbnRlcl9zZF9jaGFuZ2UgPC0gaW5uZXJfam9pbihzdW1tZXJfdmFyaWFuY2UsIHdpbnRlcl92YXJpYW5jZSkgJT4lIAogICMgcmVjb2RlIGxvd2VzdCBzZCB2YWx1ZXMgdG8gMC4xIGtXaCAoYXMgZWZmZWN0aXZlbHkgbm9uZSkKICBtdXRhdGUoc3VtbWVyX3NkX2ltcHV0ZWQgPSBpZl9lbHNlKHN1bW1lcl9zZCA8IDAuMSwgMC4xLCBzdW1tZXJfc2QpLAogICAgICAgICB3aW50ZXJfc2RfaW1wdXRlZCA9IGlmX2Vsc2Uod2ludGVyX3NkIDwgMC4xLCAwLjEsIHdpbnRlcl9zZCkpICU+JSAKICAjIGNhbGN1bGF0ZSBmb2xkIGNoYW5nZSAod2l0aCBjb25kaXRpb24gdG8gc2V0IH4wLyB+MCBhcyAwKQogIG11dGF0ZSh3aW50ZXJfZm9sZF9jaGdfc2QgPSBpZl9lbHNlKHdpbnRlcl9zZCA8IDAuMSAmIHN1bW1lcl9zZCA8IDAuMSwgMCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgd2ludGVyX3NkX2ltcHV0ZWQgLyBzdW1tZXJfc2RfaW1wdXRlZCkpCgp3aW50ZXJfc2RfY2hhbmdlICU+JQogICNmaWx0ZXIod2ludGVyX3NkIDwgMC4xICYgc3VtbWVyX3NkIDwgMC4xKQogIGZpbHRlcih3aW50ZXJfZm9sZF9jaGdfc2QgPT0gMCkgCiAgIyBhbGwgOSB6ZXJvIHZhbHVlcyBhcmUgaW5mZXJyZWQgZm9yIHRoZSA5IGhob3VzZWhvbGRzIHdpdGggYm90aCBsb3cKCndpbnRlcl9zZF9jaGFuZ2UgJT4lIAogIGdncGxvdCgpICsKICBnZW9tX2hpc3RvZ3JhbShhZXMoeCA9IHdpbnRlcl9mb2xkX2NoZ19zZCksIGJpbnMgPSAzMCkKYGBgCgpSaWdodCBza2V3ZWQgd2l0aCBhIGZldyBoaWdoZXIgdmFsdWVzLCBidXQgcmVhc29uYWJsZSBzY2FsZSBmb3IgZm9sZCBjaGFuZ2UgKDAgdG8gODApCgojIyMgSm9pbiBzdW1tYXJ5IGRhdGEKClRoZSBhYm92ZSBmZWF0dXJlIGVuZ2luZWVyaW5nIGNyZWF0ZWQgbXVsdGlwbGUgdGFibGVzIHdpdGggaG91c2Vob2xkIGlkIGFuZCBzdW1tYXJ5IHN0YXQocyksIGFzIGxpc3RlZCBiZWxvdywgYW5kIG9mIHdoaWNoIHRoZSAqKiB0YWJsZXMgY29udGFpbiB0aGUgZmVhdHVyZXMgdG8gam9pbiBpbiBhIHN1bW1hcnkgdGFibGUgZm9yIGNsdXN0ZXJpbmc6CgoqIHN1bW1lcl9tZWFuCiogKip3aW50ZXJfbWVhbgoqIHN1bW1lcl92YXJpYW5jZQoqIHdpbnRlcl92YXJpYW5jZQoqIHdpbnRlcl93ZWVrZW5kX21lYW4KKiB3aW50ZXJfd2Vla2RheV9tZWFuCiogc3VtbWVyX3dlZWtlbmRfbWVhbgoqIHN1bW1lcl93ZWVrZGF5X21lYW4KKiAqKnN1bW1lcl93a2VuZF9jaGcKKiAqKndpbnRlcl93a2VuZF9jaGcKKiAqKndpbnRlcl9tZWFuX2NoYW5nZQoqICoqd2ludGVyX3NkX2NoYW5nZQoKYGBge3J9CiMgYnVpbGQgc3VtbWFyeSB0YWJsZSwgdHlwZSBvZiBqb2luIGRvZXNuJ3QgbWF0dGVyCnN1bW1hcnlfZGYgPC0gaW5uZXJfam9pbih3aW50ZXJfbWVhbiwgc3VtbWVyX3drZW5kX2NoZykgJT4lIAogIGlubmVyX2pvaW4od2ludGVyX3drZW5kX2NoZykgJT4lIAogIGlubmVyX2pvaW4od2ludGVyX21lYW5fY2hhbmdlKSAlPiUgCiAgaW5uZXJfam9pbih3aW50ZXJfc2RfY2hhbmdlKSAlPiUgCiAgIyByZW1vdmUgY29sdW1ucyB0aGF0IGxlYWQgdG8gbXVsdGljb2xpbmVhcml0eQogIHNlbGVjdChsY19saWQsIHdpbnRlcl9tZWFuX2t3aCwgCiAgICAgICAgIHN1bW1lcl93a2VuZF9wY19jaGFuZ2UsIHdpbnRlcl93a2VuZF9wY19jaGFuZ2UsCiAgICAgICAgIHdpbnRlcl9mb2xkX2NoYW5nZSwgd2ludGVyX2ZvbGRfY2hnX3NkKQpgYGAKCmBgYHtyfQpjb2xuYW1lcyhzdW1tYXJ5X2RmKQpgYGAKCmBgYHtyfQpkaW0oc3VtbWFyeV9kZikKYGBgCgpTdW1tYXJ5IHRhYmxlIGNvbnRhaW5zIGRhdGEgb24gNTA3OCBob3VzZWhvbGRzLCB3aXRoIGlkIHBsdXMgNSBhdHRyaWJ1dGVzCgojIyBLLW1lYW5zIGNsdXN0ZXJpbmcgb3B0aW1pc2F0aW9uCgpTdGVwcyBmb3IgSy1tZWFuczoKCjEuIEhhdmUgbmFtZWQgcm93cwoyLiBTY2FsZSBkYXRhCjMuIFVuZGVyc3RhbmQgY29ycmVsYXRpb25zIGJldHdlZW4gdmFycyAocmVtZW1iZXIgbm8gb3V0Y29tZSB2YXIgaGVyZSkgdG8gaGVscCB1cyB1bmRlcnN0YW5kIG91ciBkYXRhCjQuIERvIGstbWVhbnMgY2x1c3RlcmluZyBhbmQgZmluZCB3aGljaCBjbHVzdGVycyBlYWNoIHJvdyBiZWxvbmcgdG8KNS4gRG8gay1tZWFucyBjbHVzdGVyaW5nIGZvciByYW5nZSBvZiBrIHZhbHVlcyBhbmQgZmluZCBvdXQgd2hpY2ggbnVtYmVyIG9mIGNsdXN0ZXJzIGJlc3QgZml0cyB0aGUgZGF0YSAoYWNjb3JkaW5nIHRvIGRpZmZlcmVudCBtZXRob2RzKQo2LiBQbG90IHRoZSBkYXRhIGFuZCBjb2xvdXIgYnkgY2x1c3RlciB0byB2aXN1YWxpc2UKCiMjIyAxKzIgZGF0YSBwcmVwIGZvciBrLW1lYW5zIGNsdXN0ZXJpbmcKCmBgYHtyfQpzdW1tYXJ5X2RmICU+JSAKICAjIG5hbWUgdGhlIHJvd3Mgd2l0aCBob3VzZWhvbGQgaWQKICBjb2x1bW5fdG9fcm93bmFtZXMoImxjX2xpZCIpICU+JQogICMgbWVhbl9rd2ggYW5kIHdpbnRlciBjaGFuZ2VzIChtZWFuLCBzZCkgYXJlIGFsbCByaWdodC1za2V3ZWQgc28gbG9nIHRyYW5zZm9ybSB0aGVzZSB2YWx1ZXMKICBtdXRhdGUoYWNyb3NzKC5jb2xzID0gYyh3aW50ZXJfbWVhbl9rd2gsIHdpbnRlcl9mb2xkX2NoYW5nZSwgd2ludGVyX2ZvbGRfY2hnX3NkKSwKICAgICAgICAgICAgICAgIC5mbnMgPSB+IGxvZygueCArIDEpLCAjIHBsdXMgMSB0byBhdm9pZCBOYU4gYW5kIC1JbmYKICAgICAgICAgICAgICAgIC5uYW1lcyA9ICJsb2dfey5jb2x9X3AxIikpICU+JSAKICBza2ltcjo6c2tpbSgpCmBgYAoKbG9nKHZhbHVlKSAtLT4gTmFOIChpZiB2YWx1ZSA8MCkgb3IgLUluZiAoaWYgdmFsdWUgPT0gMCksIHNvICsxIHRvIGFsbCB2YWx1ZXMgdG8gZW5zdXJlID4gMC4KCmkuZS4gNzYyIE5hTnMgaW4gbG9nKHdpbnRlcl9mb2xkX2NoYW5nZSkgYmVjYXVzZSBoYXZlIHRoZXkgaGF2ZSBuZWdhdGl2ZSBmb2xkIGNoYW5nZSB2YWx1ZXMsIGxvd2VzdCBpcyAtMC45Miwgc28gKzEgdG8gYWxsIHZhbHVlcyBpcyBzdWZmaWNpZW50IGZvciBsb2cgdHJhbnNmb3JtYXRpb24gKGxvZyh2YWx1ZSArIDEpIC0tPiBuZXcgIm5vIGNoYW5nZSIgb3IgMCBmb2xkX2NoYW5nZSBpcyBub3cgbG9nKDEpKS4KCmBgYHtyfQojIHByZXAgZGF0YSBmb3IgY2x1c3RlcmluZwpzdW1tYXJ5X2RmX2xvZyA8LSBzdW1tYXJ5X2RmICU+JSAKICAjIG1lYW5fa3doIGFuZCB3aW50ZXIgY2hhbmdlcyAobWVhbiwgc2QpIGFyZSBhbGwgcmlnaHQtc2tld2VkIHNvIGxvZyB0cmFuc2Zvcm0gdGhlc2UgdmFsdWVzCiAgbXV0YXRlKGFjcm9zcyguY29scyA9IGMod2ludGVyX21lYW5fa3doLCB3aW50ZXJfZm9sZF9jaGFuZ2UsIHdpbnRlcl9mb2xkX2NoZ19zZCksCiAgICAgICAgICAgICAgICAuZm5zID0gfiBsb2coLnggKyAxKSwgIyBwbHVzIDEgdG8gYXZvaWQgTmFOIGFuZCAtSW5mCiAgICAgICAgICAgICAgICAubmFtZXMgPSAibG9nX3suY29sfV9wMSIpKQoKc3VtbWFyeV9kZl9sb2cKYGBgCgpgYGB7cn0Kdmlldyhza2ltcjo6c2tpbShzdW1tYXJ5X2RmX2xvZykpCmBgYAoKbG9nX3dpbnRlcl9mb2xkX2NoZ19zZF9wMSBpcyBzdGlsbCByaWdodC1za2V3ZWQsIGJ1dCB0aGUgb3RoZXIgdHdvIGFyZSBtb3JlIG5vcm1hbCBub3cuCgpgYGB7cn0KY29sbmFtZXMoc3VtbWFyeV9kZl9sb2cpCmBgYAoKYGBge3J9CiMgcHJlcCBkYXRhIGZvciBjbHVzdGVyaW5nCmRmX2NsdXN0ZXIgPC0gc3VtbWFyeV9kZl9sb2cgJT4lICAgCiAgIyBuYW1lIHRoZSByb3dzIHdpdGggaG91c2Vob2xkIGlkCiAgY29sdW1uX3RvX3Jvd25hbWVzKCJsY19saWQiKSAlPiUKICAjIGtlZXAgb25seSB0aGUgdmFycyBvZiBpbnRlcmVzdCBmb3IgY2x1c3RlcmluZwogIHNlbGVjdChsb2dfd2ludGVyX21lYW5fa3doX3AxLCBsb2dfd2ludGVyX2ZvbGRfY2hhbmdlX3AxLCBsb2dfd2ludGVyX2ZvbGRfY2hnX3NkX3AxLCBzdW1tZXJfd2tlbmRfcGNfY2hhbmdlLCB3aW50ZXJfd2tlbmRfcGNfY2hhbmdlKSAlPiUgCiAgIyBzY2FsZSB0aGUgZGF0YSAobm9ybWFsIGFyb3VuZCBtZWFuLCBzZCAwLDEpCiAgbXV0YXRlKGFjcm9zcyh3aGVyZShpcy5udW1lcmljKSwgc2NhbGUpKQpgYGAKCmBgYHtyfQojIHNlZSBjb3JyZWxhdGlvbnMgYmV0d2VlbiB2YWx1ZXMKY29ycnBsb3QoY29yKGRmX2NsdXN0ZXIpLCBtZXRob2QgPSAibnVtYmVyIiwgdHlwZSA9ICJsb3dlciIpCmBgYAoKTW9kZXJhdGUgcG9zaXRpdmUgY29ycmVsYXRpb25zIGJldHdlZW46CgoqIFN1bW1lciBhbmQgd2ludGVyIHdlZWtlbmQgdiB3ZWVrZGF5IGNoYW5nZQoqIFdpbnRlci9zdW1tZXIgZm9sZCBjaGFuZ2UgfiB3aW50ZXIgbWVhbgoqIFdpbnRlci9zdW1tZXIgY2hhbmdlIGluIHNkIH4gV2ludGVyL3N1bW1lciBmb2xkIGNoYW5nZQoKYGBge3J9CiMgU3VtbWVyIGFuZCB3aW50ZXIgd2Vla2VuZCB2IHdlZWtkYXkgY2hhbmdlCnN1bW1hcnlfZGZfbG9nICU+JSAKICBnZ3Bsb3QoKSArCiAgZ2VvbV9wb2ludChhZXMoeCA9IHN1bW1lcl93a2VuZF9wY19jaGFuZ2UsIHkgPSB3aW50ZXJfd2tlbmRfcGNfY2hhbmdlKSkKYGBgCgpgYGB7cn0KIyBXaW50ZXIvc3VtbWVyIGZvbGQgY2hhbmdlIH4gd2ludGVyIG1lYW4Kc3VtbWFyeV9kZl9sb2cgJT4lIAogIGdncGxvdCgpICsKICBnZW9tX3BvaW50KGFlcyh4ID0gbG9nX3dpbnRlcl9tZWFuX2t3aF9wMSwgeSA9IGxvZ193aW50ZXJfZm9sZF9jaGFuZ2VfcDEpKQpgYGAKCgpgYGB7cn0KIyBXaW50ZXIvc3VtbWVyIGNoYW5nZSBpbiBzZCB+IFdpbnRlci9zdW1tZXIgZm9sZCBjaGFuZ2UKc3VtbWFyeV9kZl9sb2cgJT4lIAogIGdncGxvdCgpICsKICBnZW9tX3BvaW50KGFlcyh4ID0gbG9nX3dpbnRlcl9mb2xkX2NoYW5nZV9wMSwgeSA9IGxvZ193aW50ZXJfZm9sZF9jaGdfc2RfcDEpKQpgYGAKCmZvciBhbGwgdGhyZWUsIG1vc3QgaG91c2Vob2xkcyBhcmUgaW4gYSBzaW5nbGUgY2x1c3RlciBpbiB0aGUgbWlkZGxlLCBzb21lIHZhbHVlcyBvdXQgb24gdGhlIGVkZ2VzLgoKTm90IGNsZWFyIGNsdXN0ZXJzIGhlcmUsIGJ1dCBjb3VsZCBiZSBncm91cHMgaW4gbXVsdGktZGltZW5zaW9uYWwgc3BhY2VzIChlLmcuICJhdmVyYWdlIiBob3VzZWhvbGRzIHZlcnN1cyB0aG9zIG9uIHRoZSBlZGdlKQoKIyMjIEsgbWVhbnMgb3B0aW1pc2F0aW9uCgpgYGB7cn0KbWF4X2sgPC0gMTAKCmtfY2x1c3RlcnMgPC0gdGliYmxlKGsgPSAxOm1heF9rKSAlPiUgCiAgbXV0YXRlKGtjbHVzdCA9IG1hcChrLCB+IGttZWFucyhkZl9jbHVzdGVyLCAueCwgaXRlci5tYXggPSAxNSwgbnN0YXJ0ID0gMjUpKSwKICAgICAgICAgdGlkaWVkID0gbWFwKGtjbHVzdCwgdGlkeSksCiAgICAgICAgIGdsYW5jZWQgPSBtYXAoa2NsdXN0LCBnbGFuY2UpLAogICAgICAgICBhdWdtZW50ZWQgPSBtYXAoa2NsdXN0LCBhdWdtZW50LCBkZl9jbHVzdGVyKSkKCmtfY2x1c3RlcnMKYGBgCgoKIyMjIEV2YWx1YXRpb246IGVsYm93LCBzaWxob3VldHRlIG1ldGhvZHMKCmV2YWx1dGUgdXNpbmcgdGhlc2UgMiBtZXRob2RzLCBza2lwIGdhcCBzdGF0ICh0b28gaGlnaCBwcm9jZXNzaW5nIGRlbWFuZCkKCiMjIyMgRWxib3cgbWV0aG9kOgoKVGhlIGdvYWwgaGVyZSBpcyB0byBmaW5kIHRoZSBtaW5pbXVtIHRvdC53aXRoaW5zcyBidXQgd2l0aCB0aGUgZmV3ZXN0IGNsdXN0ZXJzIHBvc3NpYmxlIC0gaXTigJlzIGEgY29zdCBmdW5jdGlvbiB0byBmaW5kIHRoZSBiZXN0IGdhaW4gKGhlcmUsIGxvc3MhKSBpbiB0b3Qud2l0aGluc3MgYXQgdGhlIGxlYXN0IGNvc3QgaW4gYWRkaW5nIGsuCgpgYGB7cn0KY2x1c3RlcmluZyA8LSBrX2NsdXN0ZXJzICU+JSAKICB1bm5lc3QoZ2xhbmNlZCkKCmNsdXN0ZXJpbmcKYGBgCgpgYGB7cn0KZ2dwbG90KGNsdXN0ZXJpbmcsIGFlcyh4ID0gaywgeSA9IHRvdC53aXRoaW5zcykpICsKICBnZW9tX3BvaW50KCkgKwogIGdlb21fbGluZSgpICsKICBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzID0gc2VxKDEsIDIwLCBieSA9IDEpKQpgYGAKCkVsYm93IGF0IGsgPSA0CgojIyMjIFNpbGhvdWV0dGUgbWV0aG9kCgpOb3RlOiBzaWxob3VldHRlIG1ldGhvZCBnaXZlcyBhIG1lYXN1cmUgb2YgaG93IHNpbWlsYXIgYW4gb2JqZWN0IGlzIHRvIGl0cyBvd24gY2x1c3RlciAoY29oZXNpb24pIGNvbXBhcmVkIHRvIG90aGVyIGNsdXN0ZXJzIChzZXBhcmF0aW9uKSDigJMgaHR0cHM6Ly90b3dhcmRzZGF0YXNjaWVuY2UuY29tL3NpbGhvdWV0dGUtbWV0aG9kLWJldHRlci10aGFuLWVsYm93LW1ldGhvZC10by1maW5kLW9wdGltYWwtY2x1c3RlcnMtMzc4ZDYyZmY2ODkxCgpgYGB7cn0KZnZpel9uYmNsdXN0KGRmX2NsdXN0ZXIsCiAgICAgICAgICAgICBrbWVhbnMsCiAgICAgICAgICAgICBtZXRob2QgPSAic2lsaG91ZXR0ZSIsCiAgICAgICAgICAgICBuc3RhcnQgPSAyNSkKYGBgCgpUaGlzIHN1Z2dlc3Qgb3B0aW1hbCBrIGlzIDIuCgpWaXN1YWxseSBpbnNwZWN0IGsgPSAyLCBrID0gMywgayA9IDQKCiMjIyMgc3VtbWVyIHYgd2ludGVyIHdlZWtlbmRzCgpgYGB7cn0KIyBrID0gMgpjbHVzdGVyaW5nICU+JSAKICB1bm5lc3QoY29scyA9IGMoYXVnbWVudGVkKSkgJT4lIAogIGZpbHRlcihrIDw9IDIpICU+JSAKICBnZ3Bsb3QoYWVzKHggPSBzdW1tZXJfd2tlbmRfcGNfY2hhbmdlLCB5ID0gd2ludGVyX3drZW5kX3BjX2NoYW5nZSkpICsKICBnZW9tX3BvaW50KGFlcyhjb2xvdXIgPSAuY2x1c3RlciwgZmlsbCA9IC5jbHVzdGVyKSwgc2l6ZSA9IDEsIGFscGhhID0gMC4yKSArCiAgc2NhbGVfY29sb3VyX21hbnVhbCh2YWx1ZXMgPSBjKCJtZWRpdW1ibHVlIiwgImdvbGRlbnJvZDEiKSkgKwogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGMoIm1lZGl1bWJsdWUiLCAiZ29sZGVucm9kMSIpKQoKIyBrID0gMwpjbHVzdGVyaW5nICU+JSAKICB1bm5lc3QoY29scyA9IGMoYXVnbWVudGVkKSkgJT4lIAogIGZpbHRlcihrIDw9IDMpICU+JSAKICBnZ3Bsb3QoYWVzKHggPSBzdW1tZXJfd2tlbmRfcGNfY2hhbmdlLCB5ID0gd2ludGVyX3drZW5kX3BjX2NoYW5nZSkpICsKICBnZW9tX3BvaW50KGFlcyhjb2xvdXIgPSAuY2x1c3RlciwgZmlsbCA9IC5jbHVzdGVyKSwgc2l6ZSA9IDEsIGFscGhhID0gMC4yKSArCiAgc2NhbGVfY29sb3VyX21hbnVhbCh2YWx1ZXMgPSBjKCJtZWRpdW1ibHVlIiwgImdvbGRlbnJvZDEiLCAiZGVlcHBpbmsxIikpICsKICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjKCJtZWRpdW1ibHVlIiwgImdvbGRlbnJvZDEiLCAiZGVlcHBpbmsxIikpICMrCiAgI2ZhY2V0X3dyYXAofiAuY2x1c3RlciwgbmNvbCA9IDEpCgojIGsgPSA0CmNsdXN0ZXJpbmcgJT4lIAogIHVubmVzdChjb2xzID0gYyhhdWdtZW50ZWQpKSAlPiUgCiAgZmlsdGVyKGsgPD0gNCkgJT4lIAogICNmaWx0ZXIoLmNsdXN0ZXIgPT0gNCkgJT4lICAjIGZpbHRlciB0byBzZWUgZWFjaCBjbHVzdGVyCiAgZ2dwbG90KGFlcyh4ID0gc3VtbWVyX3drZW5kX3BjX2NoYW5nZSwgeSA9IHdpbnRlcl93a2VuZF9wY19jaGFuZ2UpKSArCiAgZ2VvbV9wb2ludChhZXMoY29sb3VyID0gLmNsdXN0ZXIsIGZpbGwgPSAuY2x1c3RlciksIHNpemUgPSAxLCBhbHBoYSA9IDAuMikgKwogIHNjYWxlX2NvbG91cl9tYW51YWwodmFsdWVzID0gYygibWVkaXVtYmx1ZSIsICJnb2xkZW5yb2QxIiwgImRlZXBwaW5rMSIsICJsaWdodGdyZWVuIikpICsKICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjKCJtZWRpdW1ibHVlIiwgImdvbGRlbnJvZDEiLCAiZGVlcHBpbmsxIiwgImxpZ2h0Z3JlZW4iKSkgKwogIGZhY2V0X3dyYXAofiAuY2x1c3RlcikKYGBgCgpXaXRoIGsgPSAyIC0tPiB0aGVyZSBpcyBubyByZWFsIGRpc3RpbmN0aW9uIGFsb25nIHRoaXMgcGxhbmUKCldpdGggayA9IDMgLS0+IHRoZXJlIGlzIGNsZWFyIHNlcGFyYXRpb24gb2YgY2x1c3RlciAxIGludG8gbG93IGNoYW5nZXJzIGFuZCBjbHVzdGVyIDIgaW50byBoaWdoIGNoYW5nZXJzLgoKV2l0aCBrID0gNCAtLT4gY2x1c3RlcnMgMyBhbmQgNCBhcmUgImxvdyB3ZWVrZGF5LXR5cGUgY2hhbmdlcnMiIChpLmUuIGNsdXN0ZXJlZCBpbiBib3R0b20tbGVmdCBvZiB0aGUgbWFzcyksIGNsdXN0ZXJzIDEgYW5kIDIgY292ZXIgbW9zdCBvZiB0aGUgbWFpbiBtYXNzIG9mIHBvaW50cywgbm90IG5lY2Vzc2FyaWx5ICJoaWdoIGNoYW5nZXJzIgoKIyMjIyB3aW50ZXIgbWVhbiB+IHdpbnRlci9zdW1tZXIgY2hhbmdlCgpgYGB7cn0KIyBrID0gMgpjbHVzdGVyaW5nICU+JSAKICB1bm5lc3QoY29scyA9IGMoYXVnbWVudGVkKSkgJT4lIAogIGZpbHRlcihrIDw9IDIpICU+JSAKICBnZ3Bsb3QoYWVzKHggPSBsb2dfd2ludGVyX21lYW5fa3doX3AxLCB5ID0gbG9nX3dpbnRlcl9mb2xkX2NoYW5nZV9wMSkpICsKICBnZW9tX3BvaW50KGFlcyhjb2xvdXIgPSAuY2x1c3RlciwgZmlsbCA9IC5jbHVzdGVyKSwgc2l6ZSA9IDEsIGFscGhhID0gMC4yKSArCiAgc2NhbGVfY29sb3VyX21hbnVhbCh2YWx1ZXMgPSBjKCJtZWRpdW1ibHVlIiwgImdvbGRlbnJvZDEiKSkgKwogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGMoIm1lZGl1bWJsdWUiLCAiZ29sZGVucm9kMSIpKQoKIyBrID0gMwpjbHVzdGVyaW5nICU+JSAKICB1bm5lc3QoY29scyA9IGMoYXVnbWVudGVkKSkgJT4lIAogIGZpbHRlcihrIDw9IDMpICU+JSAKICBnZ3Bsb3QoYWVzKHggPSBsb2dfd2ludGVyX21lYW5fa3doX3AxLCB5ID0gbG9nX3dpbnRlcl9mb2xkX2NoYW5nZV9wMSkpICsKICBnZW9tX3BvaW50KGFlcyhjb2xvdXIgPSAuY2x1c3RlciwgZmlsbCA9IC5jbHVzdGVyKSwgc2l6ZSA9IDEsIGFscGhhID0gMC4yKSArCiAgc2NhbGVfY29sb3VyX21hbnVhbCh2YWx1ZXMgPSBjKCJtZWRpdW1ibHVlIiwgImdvbGRlbnJvZDEiLCAiZGVlcHBpbmsxIikpICsKICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjKCJtZWRpdW1ibHVlIiwgImdvbGRlbnJvZDEiLCAiZGVlcHBpbmsxIikpICsKICBmYWNldF93cmFwKH4gLmNsdXN0ZXIpCgojIGsgPSA0CmNsdXN0ZXJpbmcgJT4lIAogIHVubmVzdChjb2xzID0gYyhhdWdtZW50ZWQpKSAlPiUgCiAgZmlsdGVyKGsgPD0gNCkgJT4lIAogICNmaWx0ZXIoLmNsdXN0ZXIgPT0gNCkgJT4lICAjIGZpbHRlciB0byBzZWUgZWFjaCBjbHVzdGVyCiAgZ2dwbG90KGFlcyh4ID0gbG9nX3dpbnRlcl9tZWFuX2t3aF9wMSwgeSA9IGxvZ193aW50ZXJfZm9sZF9jaGFuZ2VfcDEpKSArCiAgZ2VvbV9wb2ludChhZXMoY29sb3VyID0gLmNsdXN0ZXIsIGZpbGwgPSAuY2x1c3RlciksIHNpemUgPSAxLCBhbHBoYSA9IDAuMikgKwogIHNjYWxlX2NvbG91cl9tYW51YWwodmFsdWVzID0gYygibWVkaXVtYmx1ZSIsICJnb2xkZW5yb2QxIiwgImRlZXBwaW5rMSIsICJsaWdodGdyZWVuIikpICsKICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjKCJtZWRpdW1ibHVlIiwgImdvbGRlbnJvZDEiLCAiZGVlcHBpbmsxIiwgImxpZ2h0Z3JlZW4iKSkgKwogIGZhY2V0X3dyYXAofiAuY2x1c3RlcikKYGBgCgpXaXRoIGsgPSAyLCBjbHVzdGVyIDIgc2VlbXMgdG8gYmUgbm8vbG93IGNoYW5nZSBpbiB3aW50ZXIvc3VtbWVyIG1lYW4sIHdoaWxlIGNsdXN0ZXIgMSBpcyBtb3JlIHRvcC1yaWdodCAoaGlnaGVyIHdpbnRlciBtZWFuLCBoaWdoZXIgd2ludGVyL3N1bW1lciBmb2xkIGNoYW5nZSAtLSByZW1lbWJlciB0aGlzIGlzIHRyYW5zZm9ybWVkIGRhdGE6IHNjYWxlKGxvZyh4ICsgMSkpKQoKV2l0aCBrID0gMywgY2x1c3RlciAzIGlzIHRoZSBoaWdoZXIgZ3JvdXAsIGNsdXN0ZXIgMiBpcyBsb3dlci9ubyBjaGFuZ2UsIGNsdXN0ZXIgMSBpcyBhY3Jvc3MgdGhlIHdob2xlIG1hc3Mgb2YgcG9pbnRzCgpXaXRoIGsgPSA0LCBjbHVzdGVyIDQgcGlja3Mgb3V0IGhpZ2hlciBtZWFuIHdpdGggbG93L25vIGNoYW5nZSwgdGhlIG90aGVyIGNsdXN0ZXJzIGRvbid0IGxvb2sgcGFydGljdWxhcmx5IG1lYW5pbmdmdWwKCgojIyMjIHdpbnRlci9zdW1tZXIgY2hhbmdlIGluIG1lYW4gdiBzZAoKYGBge3J9CiMgayA9IDIKY2x1c3RlcmluZyAlPiUgCiAgdW5uZXN0KGNvbHMgPSBjKGF1Z21lbnRlZCkpICU+JSAKICBmaWx0ZXIoayA8PSAyKSAlPiUgCiAgZ2dwbG90KGFlcyh4ID0gbG9nX3dpbnRlcl9mb2xkX2NoYW5nZV9wMSwgeSA9IGxvZ193aW50ZXJfZm9sZF9jaGdfc2RfcDEpKSArCiAgZ2VvbV9wb2ludChhZXMoY29sb3VyID0gLmNsdXN0ZXIsIGZpbGwgPSAuY2x1c3RlciksIHNpemUgPSAxLCBhbHBoYSA9IDAuMikgKwogIHNjYWxlX2NvbG91cl9tYW51YWwodmFsdWVzID0gYygibWVkaXVtYmx1ZSIsICJnb2xkZW5yb2QxIikpICsKICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjKCJtZWRpdW1ibHVlIiwgImdvbGRlbnJvZDEiKSkKCiMgayA9IDMKY2x1c3RlcmluZyAlPiUgCiAgdW5uZXN0KGNvbHMgPSBjKGF1Z21lbnRlZCkpICU+JSAKICBmaWx0ZXIoayA8PSAzKSAlPiUgCiAgZ2dwbG90KGFlcyh4ID0gbG9nX3dpbnRlcl9mb2xkX2NoYW5nZV9wMSwgeSA9IGxvZ193aW50ZXJfZm9sZF9jaGdfc2RfcDEpKSArCiAgZ2VvbV9wb2ludChhZXMoY29sb3VyID0gLmNsdXN0ZXIsIGZpbGwgPSAuY2x1c3RlciksIHNpemUgPSAxLCBhbHBoYSA9IDAuMikgKwogIHNjYWxlX2NvbG91cl9tYW51YWwodmFsdWVzID0gYygibWVkaXVtYmx1ZSIsICJnb2xkZW5yb2QxIiwgImRlZXBwaW5rMSIpKSArCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gYygibWVkaXVtYmx1ZSIsICJnb2xkZW5yb2QxIiwgImRlZXBwaW5rMSIpKSAjKwogICNmYWNldF93cmFwKH4gLmNsdXN0ZXIpCgojIGsgPSA0CmNsdXN0ZXJpbmcgJT4lIAogIHVubmVzdChjb2xzID0gYyhhdWdtZW50ZWQpKSAlPiUgCiAgZmlsdGVyKGsgPD0gNCkgJT4lIAogICNmaWx0ZXIoLmNsdXN0ZXIgPT0gNCkgJT4lICAjIGZpbHRlciB0byBzZWUgZWFjaCBjbHVzdGVyCiAgZ2dwbG90KGFlcyh4ID0gbG9nX3dpbnRlcl9mb2xkX2NoYW5nZV9wMSwgeSA9IGxvZ193aW50ZXJfZm9sZF9jaGdfc2RfcDEpKSArCiAgZ2VvbV9wb2ludChhZXMoY29sb3VyID0gLmNsdXN0ZXIsIGZpbGwgPSAuY2x1c3RlciksIHNpemUgPSAxLCBhbHBoYSA9IDAuMikgKwogIHNjYWxlX2NvbG91cl9tYW51YWwodmFsdWVzID0gYygibWVkaXVtYmx1ZSIsICJnb2xkZW5yb2QxIiwgImRlZXBwaW5rMSIsICJsaWdodGdyZWVuIikpICsKICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjKCJtZWRpdW1ibHVlIiwgImdvbGRlbnJvZDEiLCAiZGVlcHBpbmsxIiwgImxpZ2h0Z3JlZW4iKSkgKwogIGZhY2V0X3dyYXAofiAuY2x1c3RlcikKYGBgCgpXaXRoIGsgPSAyLCBjbHVzdGVycyAxIGFuZCAyIGFyZSBkaXN0aW5jdCAoYm90dG9tLWxlZnQgYW5kIHRvcC1yaWdodCkKCldpdGggayA9IDMsIGNsdXN0ZXIgMyBpcyBtb3JlIHRvcC1yaWdodCwgY2x1c3RlcnMgMSBhbmQgMiBtb3JlIGJvdHRvbS1sZWZ0CgpXaXRoIGsgPSA0LCBjbHVzdGVyIDQgaXMgc21hbGxlc3QgYW5kIHBpY2tzIG91dCB0aGUgdmFsdWVzIGFyb3VuZCAwLCBidXQgaXMgbm90IGRpc3RpbmN0IGZyb20gdGhlIG90aGVyIDMgY2x1c3RlcnMsIHdoaWNoIGFyZSBhbHNvIG5vdCBkaXN0aW5jdCBmcm9tIGVhY2ggb3RoZXIKCiMjIyBDbHVzdGVyaW5nIHN1bW1hcnkKCkZyb20gdGhlIGFib3ZlIHZpc3VhbGlzYXRpb25zLCBpdCBzZWVtcyBrPTMgaXMgb3B0aW1hbCBmb3Igc2VwYXJhdGluZyBjbHVzdGVycyBhbG9uZyBjb3JyZWxhdGluZyBheGVzIG9mIGludGVyZXN0LCBhbHRob3VnaCBpdCBpcyBub3QgY2xlYXIgdGhhdCB0aGVzZSBzZXBhcmF0aW9ucyBhcmUgdmVyeSBzdHJvbmcgb3IgbWVhbmluZ2Z1bC4KCkxvb2tpbmcgYXQgdGhlIGstbWVhbnM6CgpgYGB7cn0KazNfbWVhbnMgPC0ga19jbHVzdGVycyAlPiUgCiAgdW5uZXN0KHRpZGllZCkgJT4lIAogIGZpbHRlcihrPT0zKSAlPiUgCiAgc2VsZWN0KC1jKGtjbHVzdCwgZ2xhbmNlZCwgYXVnbWVudGVkLCBrKSkgJT4lIAogIHJlbG9jYXRlKGNsdXN0ZXIsIHNpemUpCgprM19tZWFucwpgYGAKClRyYW5zZm9ybSBrLW1lYW5zIGJhY2sgdG8gcmF3IHZhbHVlcyAtIHJldmVyc2Ugc2NhbGluZyB0aGVuICh3aGVyZSBhcHBsaWNhYmxlKSByZXZlcnNlIGxvZyBhbmQgYWRkIDEuCgpgYGB7cn0Kc3VtbWFyeV9kZl9sb2cKYGBgCgoKYGBge3J9CiMgc2NhbGUgPSB4IC0gbWVhbiAvIHNkCndpbnRlcl9tZWFuX2t3aF9tZWFuIDwtIG1lYW4oc3VtbWFyeV9kZl9sb2ckbG9nX3dpbnRlcl9tZWFuX2t3aF9wMSkKd2ludGVyX21lYW5fa3doX3NkIDwtIHNkKHN1bW1hcnlfZGZfbG9nJGxvZ193aW50ZXJfbWVhbl9rd2hfcDEpCndpbnRlcl9mb2xkX2NoYW5nZV9tZWFuIDwtIG1lYW4oc3VtbWFyeV9kZl9sb2ckbG9nX3dpbnRlcl9mb2xkX2NoYW5nZV9wMSkKd2ludGVyX2ZvbGRfY2hhbmdlX3NkIDwtIHNkKHN1bW1hcnlfZGZfbG9nJGxvZ193aW50ZXJfZm9sZF9jaGFuZ2VfcDEpCndpbnRlcl9mb2xkX2NoZ19zZF9tZWFuIDwtIG1lYW4oc3VtbWFyeV9kZl9sb2ckbG9nX3dpbnRlcl9mb2xkX2NoZ19zZF9wMSkKd2ludGVyX2ZvbGRfY2hnX3NkX3NkIDwtIHNkKHN1bW1hcnlfZGZfbG9nJGxvZ193aW50ZXJfZm9sZF9jaGdfc2RfcDEpCnN1bW1lcl93a2VuZF9wY19jaGFuZ2VfbWVhbiA8LSBtZWFuKHN1bW1hcnlfZGZfbG9nJHN1bW1lcl93a2VuZF9wY19jaGFuZ2UpCnN1bW1lcl93a2VuZF9wY19jaGFuZ2Vfc2QgPC0gc2Qoc3VtbWFyeV9kZl9sb2ckc3VtbWVyX3drZW5kX3BjX2NoYW5nZSkKd2ludGVyX3drZW5kX3BjX2NoYW5nZV9tZWFuIDwtIG1lYW4oc3VtbWFyeV9kZl9sb2ckd2ludGVyX3drZW5kX3BjX2NoYW5nZSkKd2ludGVyX3drZW5kX3BjX2NoYW5nZV9zZCAgPC0gc2Qoc3VtbWFyeV9kZl9sb2ckd2ludGVyX3drZW5kX3BjX2NoYW5nZSkKYGBgCgpgYGAge3J9CmszX21lYW5zX3VudHJhbnNmb3JtZWQgPC0gazNfbWVhbnMgJT4lIAogIG11dGF0ZSh3aW50ZXJfbWVhbl9rd2ggPSBleHAoKGxvZ193aW50ZXJfbWVhbl9rd2hfcDEgKiB3aW50ZXJfbWVhbl9rd2hfc2QpICsgd2ludGVyX21lYW5fa3doX21lYW4pIC0gMSwKICAgICAgICAgd2ludGVyX2ZvbGRfY2hhbmdlID0gZXhwKChsb2dfd2ludGVyX2ZvbGRfY2hhbmdlX3AxICogd2ludGVyX2ZvbGRfY2hhbmdlX3NkKSArIHdpbnRlcl9mb2xkX2NoYW5nZV9tZWFuKSAtIDEsCiAgICAgICAgIHdpbnRlcl9mb2xkX2NoZ19zZCA9IGV4cCgobG9nX3dpbnRlcl9mb2xkX2NoZ19zZF9wMSAqIHdpbnRlcl9mb2xkX2NoZ19zZF9zZCkgKyB3aW50ZXJfZm9sZF9jaGdfc2RfbWVhbikgLSAxKSAlPiUgCiAgbXV0YXRlKHN1bW1lcl93a2VuZF9wY19jaGFuZ2UgPSAoc3VtbWVyX3drZW5kX3BjX2NoYW5nZSAqIHN1bW1lcl93a2VuZF9wY19jaGFuZ2Vfc2QpIC0gc3VtbWVyX3drZW5kX3BjX2NoYW5nZV9tZWFuLAogICAgICAgICB3aW50ZXJfd2tlbmRfcGNfY2hhbmdlID0gKHdpbnRlcl93a2VuZF9wY19jaGFuZ2UgKiB3aW50ZXJfd2tlbmRfcGNfY2hhbmdlX3NkKSAtIHdpbnRlcl93a2VuZF9wY19jaGFuZ2VfbWVhbikgJT4lIAogIHNlbGVjdCgtYyhsb2dfd2ludGVyX21lYW5fa3doX3AxLCBsb2dfd2ludGVyX2ZvbGRfY2hhbmdlX3AxLCBsb2dfd2ludGVyX2ZvbGRfY2hnX3NkX3AxLCB3aXRoaW5zcykpICU+JSAKICAjIG5hbWUgY29scyBhcyBrM19tZWFuCiAgcmVuYW1lX3dpdGgofiBwYXN0ZTAoImszX21lYW5fIiwgLngsIHJlY3ljbGUwID0gVFJVRSksIC5jb2xzID0gLWMoY2x1c3RlcixzaXplKSkgJT4lIAogIHJlbmFtZShjbHVzdGVyX3NpemUgPSBzaXplKQoKazNfbWVhbnNfdW50cmFuc2Zvcm1lZApgYGAKClVzaW5nIGs9MyBmb3Igay1tZWFucyBjbHVzdGVyaW5nOgoKKiBDbHVzdGVyIDEgaXMgbGFyZ2VzdCAobiA9IDMsMTUxIGhvdXNlaG9sZHMpIGFuZCB0aGlzIGdyb3VwIGlzIGNlbnRyZWQgb246IHVzaW5nIGxlc3MgZW5lcmd5IGF0IHRoZSB3ZWVrZW5kIHRoYW4gd2Vla2RheSwgbm90IGNoYW5naW5nIG11Y2ggZnJvbSB3aW50ZXIgdG8gc3VtbWVyLCBhbmQgd2l0aCBhIGxvd2VyIHRoYW4gYXZlcmFnZSBlbmVyZ3kgdXNlIGluIHRoZSB3aW50ZXIgCiogQ2x1c3RlciAyIGlzIHNlY29uZCBsYXJnZXN0IChuID0gMSwxMTcgaG91c2Vob2xkcykgYW5kIHNpbWlsYXIgdG8gY2x1c3RlciAxIGluIHdpbnRlciB1c2FnZSBhbmQgd2ludGVyIHYgc3VtbWVyIGJlaGF2aW91ciwgZXhjZXB0IHRoZXkgdXNlICoqbW9yZSoqIGVuZXJneSBhdCB3ZWVrZW5kcyB0aGFuIGR1cmluZyB3ZWVrZGF5cyAoaW4gYm90aCBzdW1tZXIgYW5kIHdpbnRlcikKKiBDbHVzdGVyIDMgaXMgdGhlIHNtYWxsZXN0IChuID0gODEwIGhvdXNlaG9sZHMpIGFuZCBjaGFyYWN0ZXJpc2VkIHNpbWlsYXJseSB0byBjbHVzdGVyIDEgaW4gd2Vla2VuZCBiZWhhdmlvdXIgKG5vdCBtdWNoIGNoYW5nZSwgbm90IGFuIGluY3JlYXNlIGNvbXBhcmVkIHRvIHdlZWtkYXkpIGJ1dCB0aGV5IHVzZSAqKm1vcmUqKiB0aGFuIGF2ZXJhZ2UgZW5lcmd5IGR1cmluZyB0aGUgd2ludGVyIGFuZCBpbmNyZWFzZSB1c2FnZSBmcm9tIHN1bW1lciB0byB3aW50ZXIuCgpgYGB7cn0KIyBhZGQgdGhlIGNsdXN0ZXIgbGFiZWwgb250byB0aGUgb3JpZ2luYWwgZGF0YWZyYW1lcwoKIyByZXRyaWV2ZSB0aGUgbGFiZWxzCmNsdXN0ZXJfbGFiZWxzIDwtIGtfY2x1c3RlcnMgJT4lIAogIGZpbHRlcihrID09MykgJT4lIAogIHVubmVzdChhdWdtZW50ZWQpICU+JSAKICBzZWxlY3QoLnJvd25hbWVzLCAuY2x1c3RlcikKCiMgam9pbiB0byBzdW1tYXJ5IGRmCnN1bW1hcnlfZGZfY2x1c3RlcmVkIDwtIGxlZnRfam9pbihzdW1tYXJ5X2RmLCBjbHVzdGVyX2xhYmVscywgYnkgPSBqb2luX2J5KGxjX2xpZCA9PSAucm93bmFtZXMpKSAlPiUgCiAgbGVmdF9qb2luKGszX21lYW5zX3VudHJhbnNmb3JtZWQsIGJ5ID0gam9pbl9ieSguY2x1c3RlciA9PSBjbHVzdGVyKSkKCiMgam9pbiB0byBmdWxsIGRmIChmaWx0ZXJlZCBmb3Igb25seSBob3VzZWhvbGRzIHdpdGggY2x1c3RlcikKam9pbmVkX2NsdXN0ZXJlZCA8LSBpbm5lcl9qb2luKGpvaW5lZF9hbGwsIGNsdXN0ZXJfbGFiZWxzLCBieSA9IGpvaW5fYnkobGNfbGlkID09IC5yb3duYW1lcykpICU+JSAKICBsZWZ0X2pvaW4oazNfbWVhbnNfdW50cmFuc2Zvcm1lZCwgYnkgPSBqb2luX2J5KC5jbHVzdGVyID09IGNsdXN0ZXIpKQpgYGAKCmBgYHtyfQpqb2luZWRfY2x1c3RlcmVkICU+JSAKICBkaXN0aW5jdChsY19saWQpCmBgYAoKTm90ZSB0aGUgam9pbmVkIGRmIG9ubHkgY29udGFpbnMgdGhlIDUsMDc4IGhvdXNlaG9sZHMgdGhhdCB3ZXJlIGNsdXN0ZXJlZCAoaW5uZXJfam9pbikuCgojIyBWaXN1YWxpc2F0aW9uIGFuZCBpbnNpZ2h0cwoKRGF0YSB0byBkcmF3IGluc2lnaHRzIGZyb206CgoqIGBzdW1tYXJ5X2RmX2NsdXN0ZXJlZGAgLSAxIHJvdyBwZXIgaG91c2Vob2xkLCBzdW1tYXJpc2luZyB3aW50ZXIvc3VtbWVyIGFuZCB3ZWVrZW5kL3dlZWtkYXkgZW5lcmd5IGNvbnN1bXB0aW9uIGJlaGF2aW91ciwgd2l0aCBjbHVzdGVyIG51bWJlciBhbmQgY2x1c3RlciBtZWFucwoqIGBqb2luZWRfY2x1c3RlcmVkYCAtIDEgcm93IHBlciBkYXRlIHBlciBob3VzZWhvbGQsIHdpdGggaW5kaXZpZHVhbCBlbmVyZ3kgdXNlIGFzIHdlbGwgYXMgd2VhdGhlciBjb25kaXRpb25zLCB3aXRoIGNsdXN0ZXIgbnVtYmVyIGFuZCBjbHVzdGVyIG1lYW5zCgoKIyMjIFN1bW1hcnkgYm94cGxvdHMKCldlZWtlbmQgZWZmZWN0CgpgYGB7cn0Kc3VtbWFyeV9kZl9jbHVzdGVyZWQgJT4lIAogIG11dGF0ZSh3ZWVrZW5kX2VmZmVjdCA9IGlmX2Vsc2UoLmNsdXN0ZXIgPT0gMiwiY2x1c3RlciAyIiwiY2x1c3RlcnMgMSszIikpICU+JSAKICBnZ3Bsb3QoKSArCiAgZ2VvbV9ib3hwbG90KGFlcyh4ID0gd2Vla2VuZF9lZmZlY3QsIHkgPSBzdW1tZXJfd2tlbmRfcGNfY2hhbmdlKSkKCnN1bW1hcnlfZGZfY2x1c3RlcmVkICU+JSAKICBtdXRhdGUod2Vla2VuZF9lZmZlY3QgPSBpZl9lbHNlKC5jbHVzdGVyID09IDIsImNsdXN0ZXIgMiIsImNsdXN0ZXJzIDErMyIpKSAlPiUgCiAgZ2dwbG90KCkgKwogIGdlb21fYm94cGxvdChhZXMoeCA9IHdlZWtlbmRfZWZmZWN0LCB5ID0gd2ludGVyX3drZW5kX3BjX2NoYW5nZSkpCmBgYAoKV2ludGVyIGVmZmVjdAoKYGBge3J9CnN1bW1hcnlfZGZfY2x1c3RlcmVkICU+JSAKICBtdXRhdGUod2ludGVyX2VmZmVjdCA9IGlmX2Vsc2UoLmNsdXN0ZXIgPT0gMywiY2x1c3RlciAzIiwiY2x1c3RlcnMgMSsyIikpICU+JSAKICBnZ3Bsb3QoKSArCiAgZ2VvbV9wb2ludChhZXMoeCA9IHdpbnRlcl9tZWFuX2t3aCwgeSA9IHdpbnRlcl9mb2xkX2NoYW5nZSwgY29sb3VyID0gd2ludGVyX2VmZmVjdCkpICsKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIikKYGBgCgpgYGB7cn0Kc3VtbWFyeV9kZl9jbHVzdGVyZWQgJT4lIAogIG11dGF0ZSh3aW50ZXJfZWZmZWN0ID0gaWZfZWxzZSguY2x1c3RlciA9PSAzLCJjbHVzdGVyIDMiLCJjbHVzdGVycyAxKzIiKSkgJT4lIAogIGdncGxvdCgpICsKICBnZW9tX2JveHBsb3QoYWVzKHggPSB3aW50ZXJfZWZmZWN0LCB5ID0gd2ludGVyX2ZvbGRfY2hhbmdlKSkKCnN1bW1hcnlfZGZfY2x1c3RlcmVkICU+JSAKICBtdXRhdGUod2ludGVyX2VmZmVjdCA9IGlmX2Vsc2UoLmNsdXN0ZXIgPT0gMywiY2x1c3RlciAzIiwiY2x1c3RlcnMgMSsyIikpICU+JSAKICBnZ3Bsb3QoKSArCiAgZ2VvbV9ib3hwbG90KGFlcyh4ID0gd2ludGVyX2VmZmVjdCwgeSA9IHdpbnRlcl9tZWFuX2t3aCkpCmBgYAoKIyMjIHRpbWUgc2VyaWVzIHBsb3RzCgpgYGB7cn0KIyB0aW1lIHNlcmllcyBmb3IgaG91c2Vob2xkcyBpbiBjbHVzdGVyIDMgKHdpbnRlciBlZmZlY3QpCmpvaW5lZF9jbHVzdGVyZWQgJT4lIAogIGZpbHRlciguY2x1c3RlciA9PSAzKSAlPiUgCiAgZ3JvdXBfYnkoZGF0ZSkgJT4lIAogIG11dGF0ZShtZWRpYW5fa3doID0gbWVkaWFuKGt3aCkpICU+JSAKICB1bmdyb3VwKCkgJT4lIAogIGdncGxvdCgpICsKICBnZW9tX2xpbmUoYWVzKHggPSBkYXRlLCB5ID0ga3doLCBncm91cCA9IGxjX2xpZCksIGNvbG91ciA9ICJncmV5ODAiLCBhbHBoYSA9IDAuMSkgKwogIGdlb21fbGluZShhZXMoeCA9IGRhdGUsIHkgPSBtZWRpYW5fa3doKSwgY29sb3VyID0gImluZGlhbnJlZCIpICsKICBzY2FsZV95X2NvbnRpbnVvdXMobGltaXRzID0gYygwLDM1MCkpICsKICB0aGVtZV9jbGFzc2ljKCkKYGBgCgpgYGB7cn0KIyB0aW1lIHNlcmllcyBmb3IgaG91c2Vob2xkcyBpbiBjbHVzdGVyIDMgKHdpbnRlciBlZmZlY3QpCiMjIHpvb21lZCBpbiBvbiB5IGF4aXMKam9pbmVkX2NsdXN0ZXJlZCAlPiUgCiAgZmlsdGVyKC5jbHVzdGVyID09IDMpICU+JSAKICBncm91cF9ieShkYXRlKSAlPiUgCiAgbXV0YXRlKG1lZGlhbl9rd2ggPSBtZWRpYW4oa3doKSkgJT4lIAogIHVuZ3JvdXAoKSAlPiUgCiAgZ2dwbG90KCkgKwogIGdlb21fbGluZShhZXMoeCA9IGRhdGUsIHkgPSBrd2gsIGdyb3VwID0gbGNfbGlkKSwgY29sb3VyID0gImdyZXk4MCIsIGFscGhhID0gMC4xKSArCiAgZ2VvbV9saW5lKGFlcyh4ID0gZGF0ZSwgeSA9IG1lZGlhbl9rd2gpLCBjb2xvdXIgPSAiaW5kaWFucmVkIikgKwogIHNjYWxlX3lfY29udGludW91cyhsaW1pdHMgPSBjKDAsNTApKSArCiAgdGhlbWVfY2xhc3NpYygpCmBgYAoKCgpgYGB7cn0KIyB0aW1lIHNlcmllcyBmb3IgaG91c2Vob2xkcyBpbiBjbHVzdGVyIDIgKHdlZWtlbmQgZWZmZWN0KQpqb2luZWRfY2x1c3RlcmVkICU+JSAKICBmaWx0ZXIoLmNsdXN0ZXIgPT0gMikgJT4lIAogIGdyb3VwX2J5KGRhdGUpICU+JSAKICBtdXRhdGUobWVkaWFuX2t3aCA9IG1lZGlhbihrd2gpKSAlPiUgCiAgdW5ncm91cCgpICU+JSAKICBnZ3Bsb3QoKSArCiAgZ2VvbV9saW5lKGFlcyh4ID0gZGF0ZSwgeSA9IGt3aCwgZ3JvdXAgPSBsY19saWQpLCBjb2xvdXIgPSAiZ3JleTgwIiwgYWxwaGEgPSAwLjEpICsKICBnZW9tX2xpbmUoYWVzKHggPSBkYXRlLCB5ID0gbWVkaWFuX2t3aCksIGNvbG91ciA9ICJpbmRpYW5yZWQiKSArCiAgc2NhbGVfeV9jb250aW51b3VzKGxpbWl0cyA9IGMoMCwzNTApKSArCiAgdGhlbWVfY2xhc3NpYygpCmBgYAoKYGBge3J9CiMgdGltZSBzZXJpZXMgZm9yIGhvdXNlaG9sZHMgaW4gY2x1c3RlciAyICh3ZWVrZW5kIGVmZmVjdCkKIyMgem9vbWVkIGluIG9uIG9uZSBtb250aCAoMjAxMy0wMSkKam9pbmVkX2NsdXN0ZXJlZCAlPiUgCiAgZmlsdGVyKC5jbHVzdGVyID09IDIpICU+JQogIGdyb3VwX2J5KGRhdGUpICU+JSAKICBtdXRhdGUobWVkaWFuX2t3aCA9IG1lZGlhbihrd2gpKSAlPiUgCiAgdW5ncm91cCgpICU+JSAKICBmaWx0ZXIoZGF0ZSA+PSAiMjAxMy0wMS0wMSIgJiBkYXRlIDw9ICIyMDEzLTAxLTMxIikgJT4lIAogIGdncGxvdCgpICsKICBnZW9tX2xpbmUoYWVzKHggPSBkYXRlLCB5ID0ga3doLCBncm91cCA9IGxjX2xpZCksIGNvbG91ciA9ICJncmV5ODAiLCBhbHBoYSA9IDAuMSkgKwogIGdlb21fbGluZShhZXMoeCA9IGRhdGUsIHkgPSBtZWRpYW5fa3doKSwgY29sb3VyID0gImluZGlhbnJlZCIpICsKICBzY2FsZV95X2NvbnRpbnVvdXMobGltaXRzID0gYygwLDUwKSkgKwogIHRoZW1lX2NsYXNzaWMoKQpgYGAKCgpgYGB7cn0KIyB0aW1lIHNlcmllcyBmb3IgaG91c2Vob2xkcyBpbiBjbHVzdGVyIDEgKGxvdyBjaGFuZ2VycykKam9pbmVkX2NsdXN0ZXJlZCAlPiUgCiAgZmlsdGVyKC5jbHVzdGVyID09IDEpICU+JSAKICBncm91cF9ieShkYXRlKSAlPiUgCiAgbXV0YXRlKG1lZGlhbl9rd2ggPSBtZWRpYW4oa3doKSkgJT4lIAogIHVuZ3JvdXAoKSAlPiUgCiAgZ2dwbG90KCkgKwogIGdlb21fbGluZShhZXMoeCA9IGRhdGUsIHkgPSBrd2gsIGdyb3VwID0gbGNfbGlkKSwgY29sb3VyID0gImdyZXk4MCIsIGFscGhhID0gMC4xKSArCiAgZ2VvbV9saW5lKGFlcyh4ID0gZGF0ZSwgeSA9IG1lZGlhbl9rd2gpLCBjb2xvdXIgPSAiaW5kaWFucmVkIikgKwogIHNjYWxlX3lfY29udGludW91cyhsaW1pdHMgPSBjKDAsMzUwKSkgKwogIHRoZW1lX2NsYXNzaWMoKQpgYGAKCmBgYHtyfQojIHRpbWUgc2VyaWVzIGZvciBob3VzZWhvbGRzIGluIGNsdXN0ZXIgMSAobG93IGNoYW5nZXJzKQojIyB6b29tZWQgaW4gb24geSBheGlzCmpvaW5lZF9jbHVzdGVyZWQgJT4lIAogIGZpbHRlciguY2x1c3RlciA9PSAxKSAlPiUgCiAgZ3JvdXBfYnkoZGF0ZSkgJT4lIAogIG11dGF0ZShtZWRpYW5fa3doID0gbWVkaWFuKGt3aCkpICU+JSAKICB1bmdyb3VwKCkgJT4lIAogIGdncGxvdCgpICsKICBnZW9tX2xpbmUoYWVzKHggPSBkYXRlLCB5ID0ga3doLCBncm91cCA9IGxjX2xpZCksIGNvbG91ciA9ICJncmV5ODAiLCBhbHBoYSA9IDAuMSkgKwogIGdlb21fbGluZShhZXMoeCA9IGRhdGUsIHkgPSBtZWRpYW5fa3doKSwgY29sb3VyID0gImluZGlhbnJlZCIpICsKICBzY2FsZV95X2NvbnRpbnVvdXMobGltaXRzID0gYygwLDUwKSkgKwogIHRoZW1lX2NsYXNzaWMoKQpgYGAKCmBgYHtyfQojIHRpbWUgc2VyaWVzIGZvciBob3VzZWhvbGRzIGluIGNsdXN0ZXIgMSAobG93IGNoYW5nZXJzKQojIyB6b29tZWQgaW4gb24geSBheGlzICYgZm9yIEphbiAyMDEzIG9ubHkKam9pbmVkX2NsdXN0ZXJlZCAlPiUgCiAgZmlsdGVyKC5jbHVzdGVyID09IDEpICU+JSAKICBncm91cF9ieShkYXRlKSAlPiUgCiAgbXV0YXRlKG1lZGlhbl9rd2ggPSBtZWRpYW4oa3doKSkgJT4lIAogIHVuZ3JvdXAoKSAlPiUgCiAgZmlsdGVyKGRhdGUgPj0gIjIwMTMtMDEtMDEiICYgZGF0ZSA8PSAiMjAxMy0wMS0zMSIpICU+JSAKICBnZ3Bsb3QoKSArCiAgZ2VvbV9saW5lKGFlcyh4ID0gZGF0ZSwgeSA9IGt3aCwgZ3JvdXAgPSBsY19saWQpLCBjb2xvdXIgPSAiZ3JleTgwIiwgYWxwaGEgPSAwLjEpICsKICBnZW9tX2xpbmUoYWVzKHggPSBkYXRlLCB5ID0gbWVkaWFuX2t3aCksIGNvbG91ciA9ICJpbmRpYW5yZWQiKSArCiAgc2NhbGVfeV9jb250aW51b3VzKGxpbWl0cyA9IGMoMCw1MCkpICsKICB0aGVtZV9jbGFzc2ljKCkKYGBgCgpgYGB7cn0KY2lfbGV2ZWwgPSAwLjAxICMgZm9yIDk5JSBDSSBpbnRlcnZhbHMKCiMgY2FsYyBDSSB3aXRoIHNhbXBsZV9tZWFuICsgY2lfbGV2ZWwqKHNhbXBsZV9zZC8oc3FydChzYW1wbGVfc2l6ZSkpKQoKam9pbmVkX2NsdXN0ZXJlZCAlPiUgCiAgZ3JvdXBfYnkoLmNsdXN0ZXIsIGRhdGUpICU+JSAKICBzdW1tYXJpc2UobWVkaWFuX2t3aCA9IG1lZGlhbihrd2gpLAogICAgICAgICAgICBtZWFuX2t3aCA9IG1lYW4oa3doKSwKICAgICAgICAgICAgbG93ZXJfY2kgPSBtZWFuKGt3aCkgLSAoY2lfbGV2ZWwgKiAoc2Qoa3doKS9zcXJ0KG4oKSkpKSwKICAgICAgICAgICAgdXBwZXJfY2kgPSBtZWFuKGt3aCkgKyAoY2lfbGV2ZWwgKiAoc2Qoa3doKS9zcXJ0KG4oKSkpKSkgJT4lIAogIHVuZ3JvdXAoKSAlPiUgCiAgZ2dwbG90KGFlcyh4ID0gZGF0ZSkpICsKICBnZW9tX3JpYmJvbihhZXMoeW1pbiA9IGxvd2VyX2NpLCB5bWF4ID0gdXBwZXJfY2ksIGNvbG91ciA9IC5jbHVzdGVyKSwgYWxwaGEgPSAwLjUpICsKICBnZW9tX2xpbmUoYWVzKHkgPSBtZWFuX2t3aCwgY29sb3VyID0gLmNsdXN0ZXIpKSArCiAgdGhlbWVfY2xhc3NpYygpICsKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIikKYGBgCgo=